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']))
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!' )
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
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)
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.')
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
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'])
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()
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
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()
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
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!')
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
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)))