Example #1
0
 def symmetry(self, symprec=1e-3):
     """ Compute space group with spglib. """
     from matador.utils.cell_utils import doc2spg
     import spglib as spg
     print('Refining symmetries...')
     if self.mode == 'display':
         print_warning('{}'.format('At symprec: ' + str(symprec)))
         print_warning("{:^36}{:^16}{:^16}".format('text_id', 'new sg', 'old sg'))
     for _, doc in enumerate(self.cursor):
         try:
             spg_cell = doc2spg(doc)
             sg = spg.get_spacegroup(spg_cell, symprec=symprec).split(' ')[0]
             if sg != doc['space_group']:
                 self.changed_count += 1
                 self.diff_cursor.append(doc)
                 if self.mode == 'display':
                     print_notify("{:^36}{:^16}{:^16}"
                                  .format(doc['text_id'][0]+' '+doc['text_id'][1], sg, doc['space_group']))
                 doc['space_group'] = sg
             else:
                 if self.mode == 'display':
                     print("{:^36}{:^16}{:^16}"
                           .format(doc['text_id'][0]+' '+doc['text_id'][1], sg, doc['space_group']))
         except Exception:
             self.failed_count += 1
             if self.args.get('debug'):
                 print_exc()
                 print_failure('Failed for' + ' '.join(doc['text_id']))
Example #2
0
 def print_report(self):
     """ Print spatula report on current database. """
     try:
         report = self.report.find_one()
         print('Database last modified on', report['last_modified'],
               'with matador', report['version'] + '.')
     except Exception:
         print_warning(
             'Failed to print database report: spatula is probably running!'
         )
Example #3
0
    def __init__(
            self, cursor, swap=None, uniq=False, top=None, maintain_num_species=True, debug=False, **kwargs):
        """ Initialise class with query cursor and arguments.

        Parameters:
            cursor (list): cursor of documents to swap.

        Keyword arguments:
            swap (str): specification of swaps to perform, e.g.
                "LiP:KSn" will swap all Li->P and all K->Sn in the
                cursor.
            uniq (bool/float): filter documents by similarity with
                the default sim_tol (True) or the value provided here.
            top (int): only swap from the first `top` structures in
                the cursor.
            maintain_num_species (bool): only perform swaps that maintain
                the number of species in the structure
            debug (bool): enable debug output
            kwargs (dict): dictionary of extra arguments that should be ignored.

        """
        # define some swap macros
        self.periodic_table = get_periodic_table()
        self.maintain_num_species = maintain_num_species
        self.swap_dict_list = None
        self.swap_args = swap
        del self.periodic_table['X']
        self.template_structure = None
        self.cursor = list(cursor)
        if top is not None:
            self.cursor = self.cursor[:top]

        if len(self.cursor) == 0:
            return

        self.swap_counter = 0
        self.parse_swaps(self.swap_args)
        swap_cursor = []
        for doc in self.cursor:
            docs, counter = self.atomic_swaps(doc)
            self.swap_counter += counter
            if counter > 0:
                swap_cursor.extend(docs)
        self.cursor = swap_cursor
        if self.swap_counter > 0:
            print_success('Performed {} swaps.'.format(self.swap_counter))
        else:
            print_warning('No swaps performed.')

        if uniq:
            from matador.utils.cursor_utils import filter_unique_structures
            print('Filtering for unique structures...')
            filtered_cursor = filter_unique_structures(self.cursor, debug=debug, sim_tol=uniq)
            print('Filtered {} down to {}'.format(len(self.cursor), len(filtered_cursor)))
            self.cursor = filtered_cursor
Example #4
0
    def create_hull(self):
        """ Begin the hull creation routines and perform the
        post-processing specified by initial arguments.

        """
        if self.args.get('uniq'):
            from matador.utils.cursor_utils import filter_unique_structures
            if self.args.get('uniq') is True:
                sim_tol = 0.1
            else:
                sim_tol = self.args.get('uniq')
            print_notify('Filtering for unique structures...')
            self.cursor = filter_unique_structures(self.cursor,
                                                   args=self.args,
                                                   quiet=True,
                                                   sim_tol=sim_tol,
                                                   hull=True,
                                                   energy_key=self.energy_key)

        self.construct_phase_diagram()

        if not self.hull_cursor:
            print_warning(
                'No structures on hull with chosen chemical potentials.')
        else:
            print_notify(
                '{} structures found within {} eV of the hull, including chemical potentials.'
                .format(len(self.hull_cursor), self.hull_cutoff))

        display_results(self.hull_cursor,
                        hull=True,
                        energy_key=self.energy_key,
                        **self.args)

        if self.compute_voltages:
            print("Constructing electrode system with active ion: {}".format(
                self.species[0]))
            self.voltage_curve()

        if self.compute_volumes:
            self.volume_curve()

        if not self.args.get('no_plot'):
            if self.compute_voltages and self.voltage_data:
                self.plot_voltage_curve(show=False)
            if self.compute_volumes and self.volume_data:
                self.plot_volume_curve(show=False)

            self.plot_hull(**self.args['plot_kwargs'],
                           debug=self.args.get('debug'),
                           show=True)
Example #5
0
    def spawn(self, join=False):
        """ Spawn processes to perform calculations.

        Keyword arguments:
            join (bool): whether or not to attach to ComputeTask
                process. Useful for testing.

        """
        procs = []
        error_queue = mp.Queue()
        for proc_id in range(self.nprocesses):
            procs.append(
                mp.Process(target=self.perform_new_calculations,
                           args=(random.sample(self.file_lists['res'],
                                               len(self.file_lists['res'])),
                                 error_queue, proc_id)))
        for proc in procs:
            proc.start()
            if join:
                proc.join()

        errors = []
        failed_seeds = []

        # wait for each proc to write to error queue
        try:
            for _, proc in enumerate(procs):
                result = error_queue.get()
                if isinstance(result[1], Exception):
                    errors.append(result)
                    failed_seeds.append(result[2])

            if errors:
                error_message = ''
                for error in errors:
                    error_message += 'Process {} raised error(s): {}. '.format(
                        error[0], error[1])
                    if len({type(error[1]) for error in errors}) == 1:
                        raise errors[0][1]
                    raise type(errors[0][1])(error_message)
                raise BundledErrors(error_message)
        # the only errors that reach here are fatal, e.g. WalltimeError, CriticalError, InputError, KeyboardInterrupt
        except RuntimeError as err:
            result = [proc.join(timeout=2) for proc in procs]
            result = [proc.terminate() for proc in procs if proc.is_alive()]
            print_failure('Fatal error(s) reported:')
            print_warning(err)
            raise err

        print('Nothing left to do.')
Example #6
0
    def wrapped_plot_function(*args, **kwargs):
        """ Wrap and return the plotting function. """
        saving = False
        result = None

        # if we're going to be saving a figure, switch to Agg to avoid X-forwarding
        try:
            for arg in args:
                if arg.savefig:
                    import matplotlib
                    # don't warn as backend might have been set externally by e.g. Jupyter
                    matplotlib.use('Agg', force=False)
                    saving = True
                    break
        except AttributeError:
            pass
        if not saving:
            if any(kwargs.get(ext) for ext in SAVE_EXTS):
                import matplotlib
                matplotlib.use('Agg', force=False)
                saving = True

        settings = load_custom_settings(kwargs.get('config_fname'), quiet=True, no_quickstart=True)
        try:
            style = settings.get('plotting', {}).get('default_style')
            if kwargs.get('style'):
                style = kwargs['style']
            if style is not None and not isinstance(style, list):
                style = [style]
            if style is None:
                style = ['matador']
            if 'matador' in style:
                for ind, styles in enumerate(style):
                    if styles == 'matador':
                        style[ind] = MATADOR_STYLE

            # now actually call the function
            set_style(style)
            result = function(*args, **kwargs)

        except Exception as exc:
            if 'TclError' not in type(exc).__name__:
                raise exc

            print_failure('Caught exception: {}'.format(type(exc).__name__))
            print_warning('Error message was: {}'.format(exc))
            print_warning('This is probably an X-forwarding error')
            print_failure('Skipping plot...')

        return result
Example #7
0
    def perform_id_query(self):
        """ Query the `text_id` field for the ID provided in the args for a calc_match
        or hull/voltage query. Use the results of the text_id query to match to other
        entries that have the same calculation parameters. Sets self.query_dict and
        self.calc_dict.

        Raises:
            RuntimeError: if no structures are found.

        """

        self.cursor = []
        query_dict = dict()
        query_dict['$and'] = []
        query_dict['$and'].append(self._query_id())
        if not self.args.get('ignore_warnings'):
            query_dict['$and'].append(self._query_quality())
        self.repo = self._collection
        self.cursor = list(self._find_and_sort(query_dict))

        if not self.cursor:
            raise RuntimeError('Could not find a match with {} try widening your search.'.format(self.args.get('id')))

        if len(self.cursor) >= 1:
            display_results(list(self.cursor)[:self.top], **self.args)

            if len(self.cursor) > 1:
                print_warning('Matched multiple structures with same text_id. The first one will be used.')

            # save special copy of calc_dict for hulls
            self.calc_dict = dict()
            self.calc_dict['$and'] = []
            # to avoid deep recursion, and since this is always called first
            # don't append, just set
            self.query_dict = self._query_calc(self.cursor[0])
            if self.args.get('composition'):
                self.args['intersection'] = True
                self.query_dict['$and'].append(self._query_composition())
            self.calc_dict['$and'] = list(self.query_dict['$and'])
Example #8
0
    def __init__(self, cursor, collection=None, task=None, mode='display', **kwargs):
        """ Parses args and initiates modification.

        Parameters:
            cursor (list of dicts): matador cursor to refine.

        Keyword arguments:
            collection (Collection): mongodb collection to query/edit.
            task (str/callable): one of 'sym', 'spg', 'elem_set', 'tag', 'doi' or 'source',
                or a custom function that takes in and returns a cursor and field to modify.
            mode (str): one of 'display', 'overwrite', 'set'.

        """
        possible_tasks = ['sym', 'spg', 'elem_set', 'tag', 'doi', 'source', 'pspot', 'raw']
        possible_modes = ['display', 'overwrite', 'set']

        if mode not in possible_modes:
            print('Mode not understood, defaulting to "display".')
            mode = 'display'
        if collection is None and mode in ['overwrite', 'set']:
            raise SystemExit('Impossible to overwite or set without db collection, exiting...')
        if task is None:
            raise SystemExit('No specified task, exiting...')
        if task not in possible_tasks and not callable(task):
            raise SystemExit('Did not understand task, please choose one of ' + ', '.join(possible_tasks))
        if task == 'tag' and mode == 'set':
            raise SystemExit('Task "tags" and mode "set" will not alter the database, please use mode "overwrite".')

        self.cursor = list(cursor)
        self.diff_cursor = []
        self.collection = collection
        self.mode = mode
        self.changed_count = 0
        self.failed_count = 0
        self.args = kwargs

        if task in ['spg', 'sym']:
            if kwargs.get('symprec'):
                self.symmetry(symprec=kwargs.get('symprec'))
            else:
                self.symmetry()
            self.field = 'space_group'
        elif task == 'elem_set':
            self.elem_set()
            self.field = 'elems'
        elif task == 'tag':
            self.field = 'tags'
            self.tag = self.args.get('new_tag')
            if self.tag is None:
                print_warning('No new tag defined, nothing will be done.')
            else:
                self.add_tag()
        elif task == 'doi':
            self.field = 'doi'
            self.doi = self.args.get('new_doi')
            if self.doi is None:
                print_warning('No new DOI defined, nothing will be done.')
            else:
                self.add_doi()
        elif task == 'source':
            self.field = 'root_source'
            self.add_root_source()
        elif task == 'pspot':
            self.field = 'species_pot'
            self.tidy_pspots()
        elif task == 'raw':
            self.field = '_raw'
            self.add_raw_data()
        elif callable(task):
            print('Using custom task function: {}'.format(task))
            self.diff_cursor, self.field = task(self.cursor)

        self.changed_count = len(self.diff_cursor)
        print(self.changed_count, '/', len(self.cursor), 'to be changed.')
        print(self.failed_count, '/', len(self.cursor), 'failed.')

        if self.mode in ['set', 'overwrite'] and self.changed_count > 0:
            self.update_docs()
Example #9
0
    def __init__(self, *args, **kwargs):
        """ Initialise the query with command line arguments and return
        results.

        """
        # read args
        self.kwargs = kwargs
        self.args = vars(args[0])
        self.args['no_quickstart'] = self.kwargs.get('no_quickstart')
        self.argstr = kwargs.get('argstr')

        file_exts = ['cell', 'res', 'pdb', 'markdown', 'latex', 'param', 'xsf']
        self.export = any([self.args.get(ext) for ext in file_exts])

        self.subcommand = self.args.pop("subcmd")

        if self.subcommand != 'import':
            self.settings = load_custom_settings(
                config_fname=self.args.get('config'),
                debug=self.args.get('debug'),
                no_quickstart=self.args.get('no_quickstart'))
            result = make_connection_to_collection(
                self.args.get('db'),
                check_collection=(self.subcommand != "stats"),
                mongo_settings=self.settings)
            self.client, self.db, self.collections = result

        if self.subcommand == 'stats':
            self.stats()

        try:
            if self.subcommand == 'import':
                from matador.db import Spatula
                self.importer = Spatula(self.args)

            if self.subcommand == 'query':
                self.query = DBQuery(self.client, self.collections,
                                     **self.args)
                self.cursor = self.query.cursor

            if self.subcommand == 'swaps':
                from matador.swaps import AtomicSwapper
                self.query = DBQuery(self.client, self.collections,
                                     **self.args)
                if self.args.get('hull_cutoff') is not None:
                    self.hull = QueryConvexHull(query=self.query, **self.args)
                    self.swapper = AtomicSwapper(self.hull.hull_cursor,
                                                 **self.args)
                else:
                    self.swapper = AtomicSwapper(self.query.cursor,
                                                 **self.args)
                self.cursor = self.swapper.cursor

            if self.subcommand == 'refine':
                from matador.db import Refiner
                self.query = DBQuery(self.client, self.collections,
                                     **self.args)
                if self.args.get('hull_cutoff') is not None:
                    self.hull = QueryConvexHull(self.query, **self.args)
                    self.refiner = Refiner(self.hull.cursor, self.query.repo,
                                           **self.args)
                else:
                    self.refiner = Refiner(self.query.cursor, self.query.repo,
                                           **self.args)

                self.cursor = self.refiner.cursor

            if self.subcommand == 'hull' or self.subcommand == 'voltage':
                self.hull = QueryConvexHull(**self.args,
                                            voltage=self.subcommand ==
                                            'voltage',
                                            client=self.client,
                                            collections=self.collections)
                self.cursor = self.hull.hull_cursor

            if self.subcommand == 'changes':
                from matador.db import DatabaseChanges
                if len(self.collections) != 1:
                    raise SystemExit(
                        'Cannot view changes of more than one collection at once.'
                    )
                if self.args.get('undo'):
                    action = 'undo'
                else:
                    action = 'view'
                changeset = self.args.get('changeset')
                if changeset is None:
                    changeset = 0
                DatabaseChanges([key for key in self.collections][0],
                                changeset_ind=changeset,
                                action=action,
                                mongo_settings=self.settings,
                                override=kwargs.get('no_quickstart'))

            if self.subcommand == 'hulldiff':
                from matador.hull.hull_diff import diff_hulls
                if self.args.get('compare') is None:
                    raise SystemExit(
                        'Please specify which hulls to query with --compare.')
                diff_hulls(self.client, self.collections, **self.args)

            if self.export and self.cursor:
                from matador.export import query2files
                if self.args.get('write_n') is not None:
                    self.cursor = [
                        doc for doc in self.cursor if len(doc['stoichiometry'])
                        == self.args.get('write_n')
                    ]
                if not self.cursor:
                    print_failure('No structures left to export.')
                query2files(self.cursor,
                            **self.args,
                            argstr=self.argstr,
                            subcmd=self.subcommand,
                            hash_dupe=True)

            if self.args.get('view'):
                from matador.utils.viz_utils import viz
                if self.args.get('top') is None:
                    self.top = len(self.cursor)
                else:
                    self.top = self.args.get('top')
                if len(self.cursor[:self.top]) > 10:
                    from time import sleep
                    print_warning(
                        'WARNING: opening {} files with ase-gui...'.format(
                            len(self.cursor)))
                    print_warning(
                        'Please kill script within 3 seconds if undesired...')
                    sleep(3)
                if len(self.cursor[:self.top]) > 20:
                    print_failure(
                        'You will literally be opening that many windows, ' +
                        'I\'ll give you another 5 seconds to reconsider...')

                    sleep(5)
                    print_notify('It\'s your funeral...')
                    sleep(1)
                for doc in self.cursor[:self.top]:
                    viz(doc)

            if self.subcommand != 'import':
                self.client.close()

        except (RuntimeError, SystemExit, KeyboardInterrupt) as oops:
            if isinstance(oops, RuntimeError):
                print_failure(oops)
            elif isinstance(oops, SystemExit):
                print_warning(oops)
            try:
                self.client.close()
            except AttributeError:
                pass
            raise oops
Example #10
0
    def __init__(self, query=None, cursor=None, elements=None, species=None, voltage=False, volume=False, subcmd=None,
                 plot_kwargs=None, lazy=False, energy_key='enthalpy_per_atom', client=None, collections=None, db=None,
                 **kwargs):
        """ Initialise the class from either a DBQuery or a cursor (list
        of matador dicts) and construct the appropriate phase diagram.

        Keyword arguments:
            query (matador.query.DBQuery): object containing structures,
            cursor (list(dict)): alternatively specify list of matador documents.
            species (list(str)): list of elements/chempots to use, used to provide a useful order,
            voltage (bool): whether or nto to compute voltages relative for insertion of first entry in species,
            volume (bool): whether or not to compute volume expansion relative to first entry in species,
            energy_key (str): key under which the desired energy *per atom* is stored.
            lazy (bool): if True, do not create hull until `self.create_hull()` is called
            chempots (list(float)): list of chemical potential values to use.
            elements (list(str)): deprecated form `species`.
            kwargs (dict): mostly CLI arguments, see matador hull --help for full options.
            plot_kwargs (dict): arguments to pass to plot_hull function
            client (pymongo.MongoClient): optional client to pass to DBQuery.
            collections (dict of pymongo.collections.Collection): optional dict of collections to pass to DBQuery.
            db (str): db name to connect to in DBQuery.

        """
        self.args = dict()
        if query is not None:
            self.args.update(query.args)
        self.args.update(kwargs)

        if subcmd is not None:
            warnings.warn("subcmd will soon be deprecated, please pass the equivalent flag as a kwarg (e.g. voltage=True)")
            if subcmd == 'voltage':
                voltage = True
        self.args['subcmd'] = subcmd

        if plot_kwargs is None:
            plot_kwargs = {}
        self.args['plot_kwargs'] = plot_kwargs

        self.from_cursor = False
        self.plot_params = False

        self.compute_voltages = voltage
        self.compute_volumes = volume

        if query is None and cursor is None:
            # if no query or cursor passed, push all kwargs to new query
            from matador.query import DBQuery
            kwargs.pop('intersection', None)
            query = DBQuery(
                subcmd='hull',
                intersection=True,
                client=client,
                collections=collections,
                **kwargs
            )

        self._query = query

        if self._query is not None:
            # this isn't strictly necessary but it maintains the sanctity of the query results
            self.cursor = list(deepcopy(query.cursor))
            self.args['use_source'] = False
            if self._query.args['subcmd'] not in ['hull', 'voltage', 'hulldiff']:
                print_warning('Query was not prepared with subcmd=hull, so cannot guarantee consistent formation energies.')
        else:
            self.cursor = list(cursor)
            self.from_cursor = True
            self.args['use_source'] = True

        # set up attributes for later
        self.structures = None
        self.convex_hull = None
        self.chempot_cursor = None
        self.hull_cursor = None
        self.phase_diagram = None
        self.hull_dist = None
        self.species = None
        self.voltage_data: List[VoltageProfile] = []
        self.volume_data = defaultdict(list)
        self.elements = []
        self.num_elements = 0
        self.species = self._get_species(species, elements)
        self._dimension = len(self.species)

        # tracker for whether per_b fields have been setup
        self._per_b_done = False

        self.energy_key = energy_key
        if not self.energy_key.endswith('_per_atom'):
            warnings.warn('Appending per_atom to energy_key {}'.format(self.energy_key))
            self.energy_key += '_per_atom'

        self._extensive_energy_key = self.energy_key.split('_per_atom')[0]

        if self.args.get('hull_cutoff') is not None:
            self.hull_cutoff = float(self.args['hull_cutoff'])
        else:
            self.hull_cutoff = 0.0

        if self.cursor is None:
            raise RuntimeError('Failed to find structures to create hull!')

        self._non_elemental = False
        assert isinstance(self.species, list)
        for _species in self.species:
            if len(parse_element_string(_species, stoich=True)) > 1:
                self._non_elemental = True

        if not lazy:
            self.create_hull()
Example #11
0
def quickstart_settings():
    """ Call this if no user-specified configuration file is found. A series
    of questions will be asked to create a basic matadorrc.

    """
    print('No user-specified input found at either ~/.matadorrc or '
          '~/.config/matadorrc')
    response = input(
        'Would you like to use quickstart to create one now? [y/n] ')
    if not response.lower().startswith('y'):
        return None

    hline = 80 * '-'
    print('Okay, let\'s get started!')
    print('Firstly, where would you like me to put your config file: ')
    valid = False
    while not valid:
        response = input('[1] ~/.matadorrc, [2] ~/.config/matadorrc? ')
        if response not in ['1', '2']:
            print('I didn\'t understand, sorry... please enter 1 or 2')
        else:
            valid = True

    if response == '1':
        fname = os.path.expanduser('~/.matadorrc')
    else:
        fname = os.path.expanduser('~/.config/matadorrc')

    flines = []
    print(hline)

    response = input(
        'Great! Would you like to use matador with a MongoDB database? [y/n] ')
    if response.lower().startswith('y'):
        flines.append('mongo:')
        host_response = input(
            'Where is this MongoDB hosted? [default: localhost] ')
        if host_response == '':
            host_response = 'localhost'
        flines.append('  host: {}'.format(host_response))
        print(hline)
        port_response = input('and at what port? [default: 27017] ')
        if port_response == '':
            port_response = 27017
        flines.append('  port: {}'.format(port_response))
        print(hline)
        print('Please set your firewall and MongoDB settings accordingly: '
              'by default MongoDB\nneeds no authentication so be careful '
              'if your database is running on an open port!')
        test_conn_response = input(
            'Would you like me to test the connection to this MongoDB instance now? [y/n] '
        )
        keep_trying = True
        if test_conn_response.lower() == 'y':
            while keep_trying:
                import pymongo as pm
                cli = pm.MongoClient(host_response,
                                     port=int(port_response),
                                     maxIdleTimeMS=600000,
                                     socketTimeoutMS=3600000,
                                     serverSelectionTimeoutMS=10000,
                                     connectTimeoutMS=10000)
                try:
                    cli.database_names()
                    keep_trying = False
                except pm.errors.ServerSelectionTimeoutError:
                    print_warning(
                        'Failed to connect to {}:{}, are you sure it is running?'
                        .format(host_response, port_response))
                    response = input('Would you like me to try again? [y/n] ')
                    keep_trying = response.lower().startswith('y')
                except Exception:
                    keep_trying = False

        print(hline)
        db_response = input(
            'What is the name of the database (not collection) '
            'that you want to query/create? [default: crystals] ')
        if db_response == '':
            db_response = 'crystals'
        flines.append('  db: {}'.format(db_response))

        print(hline)
        coll_response = input(
            'What about the default collection name to query? [default: repo] '
        )
        if coll_response == '':
            coll_response = 'repo'
        flines.append('  default_collection: {}'.format(coll_response))

        print(hline)
        print('Would you like to set a protected folder for this collection?')
        file_path_response = input(
            'Any import to this collection must happen from this path, and the '
            '--force flag must be used if so. [default: None] ')
        if file_path_response != '':
            flines.append('  default_file_collection_path: {}'.format(
                file_path_response))

        print(hline)
        print_success(
            'Mongo section of matadorrc complete! Saving as {} and exiting...'.
            format(fname))
        print(hline)

    if not flines:
        print(
            'Okay, there is no config file to be made and I will keep using the defaults.'
        )
        return None

    with open(fname, 'a') as f:
        for line in flines:
            f.write(line + '\n')

    return fname
Example #12
0
    def __init__(self,
                 collection_name: str,
                 changeset_ind=0,
                 action='view',
                 override=False,
                 mongo_settings=None):
        """ Parse arguments and run changes interface.

        Parameters:
            collection_name (str): the base collection name to act upon

        Keyword arguments:
            changset_ind (int): the number of the changset to act upon (1 is oldest)
            action (str): either 'view' or 'undo'
            override (bool): override all options to positive answers for testing
            mongo_settings (dict): dictionary of already-sources mongo settings

        """
        self.changelog_name = '__changelog_{}'.format(collection_name)
        _, _, self.collections = make_connection_to_collection(
            self.changelog_name,
            allow_changelog=True,
            override=override,
            mongo_settings=mongo_settings)
        self.repo = [self.collections[key] for key in self.collections][0]
        curs = list(self.repo.find())

        if not curs:
            exit('No changesets found for {}'.format(collection_name))

        # if no changeset specified, print summary
        if changeset_ind == 0:
            self.print_change_summary(curs)

        elif changeset_ind > len(curs):
            exit('No changeset {} found for collection called "{}".'.format(
                changeset_ind, collection_name))

        # otherwise, try to act on particular changeset
        elif changeset_ind <= len(curs):
            self.change = curs[changeset_ind - 1]
            self.view_changeset(self.change, changeset_ind - 1)
            if action == 'undo':
                count = curs[changeset_ind - 1]['count']
                print_warning(
                    'An attempt will now be made to remove {} structures from {}.'
                    .format(count, collection_name))
                print_notify('Are you sure you want to do that? (y/n)')
                if override:
                    response = 'y'
                else:
                    response = input()
                if response.lower() == 'y':
                    print_notify('You don\'t have any doubts at all? (y/n)')
                    if override:
                        next_response = 'n'
                    else:
                        next_response = input()
                    if next_response.lower() == 'n':
                        print('You\'re the boss, deleting structures now...')
                    else:
                        exit('As I thought...')
                else:
                    exit()

                # proceed with deletion
                _, _, collections = make_connection_to_collection(
                    collection_name, allow_changelog=False, override=override)
                collection_to_delete_from = [
                    collections[key] for key in collections
                ][0]
                result = collection_to_delete_from.delete_many(
                    {'_id': {
                        '$in': self.change['id_list']
                    }})
                print('Deleted {}/{} successfully.'.format(
                    result.deleted_count, self.change['count']))
                print('Tidying up changelog database...')
                self.repo.delete_one({'_id': self.change['_id']})
                if not self.repo.find_one():
                    print('No structures left remaining, deleting database...')
                    collection_to_delete_from.drop()
                    self.repo.drop()
                print('Success!')
Example #13
0
def load_custom_settings(config_fname=None,
                         quiet=False,
                         debug=False,
                         no_quickstart=True):
    """ Load mongodb settings dict from file given by fname, or from
    defaults. Hierarchy of filenames to check:

        1. .matadorrc
        2. ~/.matadorrc
        3. ~/.config/matadorrc

    Keyword Arguments:
        fname (str): filename of custom settings file.
        quiet (bool): print nothing on loading.
        no_quickstart (bool): for testing purposes, override any questions.
        debug (bool): print settings on loading.

    """
    if SETTINGS.set:
        return SETTINGS

    import yaml

    if config_fname is None:
        trial_user_fnames = [
            Path(".matadorrc"),
            Path(os.path.expanduser("~/.matadorrc")),
            Path(os.path.expanduser("~/.config/matadorrc")),
            Path(__file__).parent.joinpath("matadorrc.yml"),
        ]

        # use first file that exists in hierarchy
        for fname in trial_user_fnames:
            if os.path.isfile(fname):
                config_fname = str(fname)
                break
        else:
            # otherwise offer to create one, or use default
            if not no_quickstart:
                config_fname = quickstart_settings()
            if config_fname is None:
                # otherwise load default
                print(
                    "No config provided: loading perhaps unsuitable defaults instead."
                )
                config_fname = Path(__file__).parent.joinpath("matadorrc.yml"),

        if not quiet:
            print("Loading settings from {}".format(config_fname))

    custom_settings = {}
    if os.path.isfile(config_fname):
        try:
            with open(config_fname, "r") as f:
                custom_settings = yaml.safe_load(f)
                if custom_settings is None:
                    custom_settings = {}
        except Exception:
            print_exc()
            raise SystemExit(
                "Failed to read custom settings file {} as YAML".format(
                    config_fname))
    else:
        print_warning("Could not find {}, loading default settings...".format(
            config_fname))

    settings = {}
    settings.update(DEFAULT_SETTINGS)
    settings.update(custom_settings)

    # check absolute/relative paths
    for key in FILE_PATHS:
        for setting in FILE_PATHS[key]:
            if settings.get(key) is not None:
                if settings[key].get(setting) is not None:
                    settings[key][setting] = str(
                        Path(os.path.expanduser(
                            settings[key][setting])).resolve())

    if debug:
        import json
        print(json.dumps(settings, indent=2))

    set_settings(settings, override=False)

    return SETTINGS
Example #14
0
    def perform_hull_query(self):
        """ Perform the multiple queries necessary to find possible
        calculation sets to create a convex hull from.

        Raises:
            SystemExit: if no structures are found for hull.

        """
        if self._collection is not None:
            self.repo = self._collection
            print('Creating hull from structures in query results.')
            if self.args.get('biggest'):
                print('\nFinding biggest calculation set for hull...\n')
            else:
                print('\nFinding the best calculation set for hull...')

            test_cursors = []
            test_cursor_count = []
            text_ids = []
            calc_dicts = []
            cutoff = []

            num_sample = 2
            num_rand_sample = 5 if self.args.get('biggest') else 3

            if isinstance(self.cursor, pm.cursor.Cursor):
                count = self.cursor.count()
            else:
                count = len(self.cursor)

            if count <= 0:
                raise SystemExit('No structures found for hull.')

            # generate some random indices to match to, make sure they are in order
            # so can be accessed without cursor rewinds
            sampling_indices = list(range(num_sample)) + sorted(random.sample(range(2, count), num_rand_sample))

            for ind in sampling_indices:
                doc = self.cursor[ind]
                text_ids.append(doc['text_id'])
                try:
                    self.query_dict = self._query_calc(doc)
                    cutoff.append(doc['cut_off_energy'])
                    calc_dicts.append(dict())
                    calc_dicts[-1]['$and'] = list(self.query_dict['$and'])
                    self.query_dict['$and'].append(self._query_composition())
                    if not self.args.get('ignore_warnings'):
                        self.query_dict['$and'].append(self._query_quality())

                    probe_cursor, probe_count = self._find_and_sort(self.query_dict)

                    if self._non_elemental:
                        probe_cursor = filter_cursor_by_chempots(self._chempots, probe_cursor)
                        probe_count = len(probe_cursor)

                    test_cursors.append(probe_cursor)
                    test_cursor_count.append(probe_count)

                    print("{:^24}: matched {} structures."
                          .format(' '.join(doc['text_id']), probe_count),
                          end='\t-> ')
                    print('{spin}{sedc}{functional} {cutoff} eV, {geom_force_tol} eV/A, {kpoints} 1/A.'
                          .format(spin="S-" if doc.get('spin_polarized') else '',
                                  sedc="+" + doc.get('sedc') + "+" if doc.get('sedc') else "",
                                  functional=doc["xc_functional"],
                                  cutoff=doc["cut_off_energy"],
                                  geom_force_tol=doc.get('geom_force_tol', 'xxx'),
                                  kpoints=doc.get('kpoints_mp_spacing', 'xxx')))

                    if test_cursor_count[-1] == count:
                        print('Matched all structures...')
                        break
                    if test_cursor_count[-1] > 2 * int(count / 3):
                        print('Matched at least 2/3 of total number, composing hull...')
                        break

                except Exception:
                    print_exc()
                    print_warning('Error with {}'.format(' '.join(doc['text_id'])))

            if self.args.get('biggest'):
                choice = np.argmax(np.asarray(test_cursor_count))
            else:
                # by default, find highest cutoff hull as first proxy for quality
                choice = np.argmax(np.asarray(cutoff))

            text_id = text_ids[choice]
            self.cursor = test_cursors[choice]
            self.calc_dict = calc_dicts[choice]
            if not test_cursor_count[choice]:
                raise RuntimeError('No structures found that match chemical potentials.')

            print_success('Composing hull from set containing {}'.format(' '.join(text_id)))