diff --git a/Scripts/InstallPyPackages.py b/Scripts/InstallPyPackages.py index 28efa6e..88f84c2 100755 --- a/Scripts/InstallPyPackages.py +++ b/Scripts/InstallPyPackages.py @@ -55,8 +55,9 @@ def install(*packages): upgradePip() - installFromGit(owner='ikibalin', repo='cryspy', branch='transition-to-version-0.2', egg='cryspy_0.2.0_beta') + #installFromGit(owner='ikibalin', repo='cryspy', branch='transition-to-version-0.2', egg='cryspy_0.2.0_beta') install( + 'cryspy', 'dictdiffer', 'asteval', 'pytest', diff --git a/easyInterface/Diffraction/Calculators/CryspyCalculator.py b/easyInterface/Diffraction/Calculators/CryspyCalculator.py index 892c7c8..999c4c0 100755 --- a/easyInterface/Diffraction/Calculators/CryspyCalculator.py +++ b/easyInterface/Diffraction/Calculators/CryspyCalculator.py @@ -2,14 +2,15 @@ # Copyright (c) of the author (github.com/wardsimon) # Created: 12/3/2020 -import os +import os, re from typing import Tuple, Optional import cryspy import pycifstar from asteval import Interpreter from cryspy.cif_like.cl_crystal import Crystal -from cryspy.cif_like.cl_pd import Pd, PdBackground, PdBackgroundL, PdInstrResolution, PdMeas, PdMeasL, PhaseL, Setup +from cryspy.cif_like.cl_pd import Pd, PdBackground, PdBackgroundL, PdInstrResolution, PdMeas, PdMeasL, PhaseL, Setup, \ + Chi2, DiffrnRadiation from cryspy.cif_like.cl_pd import Phase as cryspyPhase from cryspy.corecif.cl_atom_site import AtomSite, AtomSiteL from cryspy.corecif.cl_atom_site_aniso import AtomSiteAniso, AtomSiteAnisoL @@ -20,18 +21,16 @@ # Imports needed to create a cryspyObj from cryspy.scripts.cl_rhochi import RhoChi from cryspy.symcif.cl_space_group import SpaceGroup as cpSpaceGroup - -from easyInterface import logger as logging from easyInterface.Diffraction.DataClasses.DataObj.Calculation import * from easyInterface.Diffraction.DataClasses.DataObj.Experiment import * from easyInterface.Diffraction.DataClasses.PhaseObj.Phase import * from easyInterface.Diffraction.DataClasses.Utils.BaseClasses import Base from easyInterface.Utils.Helpers import time_it - +from easyInterface.Diffraction import DEFAULT_FILENAMES # Version info cryspy_version = 'Undefined' try: - from cryspy import __version__ as cryspy_version + from cryspy import __version__ as cryspy_version, Fitable, AtomSiteScat, AtomSiteScatL except ImportError: logging.logger.info('Can not find cryspy version. Using default text') CALCULATOR_INFO = { @@ -40,16 +39,15 @@ 'url': 'https://github.com/ikibalin/cryspy' } -PHASE_SEGMENT = "_phases" +PHASE_SEGMENT = "_samples" EXPERIMENT_SEGMENT = "_experiments" - class CryspyCalculator: - def __init__(self, main_rcif_path: Union[str, type(None)] = None) -> None: + def __init__(self, project_rcif_path: Union[str, type(None)] = None) -> None: self._log = logging.getLogger(__class__.__module__) self._experiment_names = [] - self._main_rcif_path = main_rcif_path - self._main_rcif = None + self._project_rcif_path = project_rcif_path + self._project_rcif = None self._phases_path = "" self._phase_names = [] self._experiments_path = "" @@ -68,6 +66,8 @@ def _createCryspyObj(self): self._log.debug('----> Start') self._log.info('Creating cryspy object') + if not self._project_rcif_path: + return RhoChi() try: phase_segment = self._parseSegment(PHASE_SEGMENT) except TypeError: @@ -106,15 +106,15 @@ def _parseSegment(self, segment: str = "") -> str: if segment not in (PHASE_SEGMENT, EXPERIMENT_SEGMENT): self._log.debug('segment not in phase or experiment') return "" - rcif_dir_name = os.path.dirname(self._main_rcif_path) + rcif_dir_name = os.path.dirname(self._project_rcif_path) try: - self._main_rcif = pycifstar.read_star_file(self._main_rcif_path) + self._project_rcif = pycifstar.read_star_file(self._project_rcif_path) except FileNotFoundError: self._log.warning('Main cif can not be found') rcif_content = "" - if segment in str(self._main_rcif): + if segment in str(self._project_rcif): self._log.debug('segment in main cif') - segment_rcif_path = os.path.join(rcif_dir_name, self._main_rcif[segment].value) + segment_rcif_path = os.path.join(rcif_dir_name, self._project_rcif[segment].value) if os.path.isfile(segment_rcif_path): with open(segment_rcif_path, 'r') as f: self._log.debug('Reading cif segment') @@ -283,6 +283,27 @@ def setPhaseDefinition(self, phases_path: str) -> NoReturn: # self._cryspy_obj.experiments[0].phase.items[0] = (new_phase_name) self._log.debug('<---- End') + def addPhaseDefinitionFromString(self, phase_rcif_content: str) -> NoReturn: + """ + Set an experiment/s to be simulated from a string. Note that this will not have any crystallographic phases + associated with it. + + :param phase_rcif_content: String containing the contents of a phase file (`.cif`) + """ + self._log.debug('----> Start') + phase = Crystal().from_cif(phase_rcif_content) + self._log.warning(f"self._cryspy_obj.crystals: {self._cryspy_obj.crystals}") + if phase is None: + self._log.error('Phase cif data is malformed') + if self._cryspy_obj.crystals is None: + self._cryspy_obj.crystals = [phase] + #self._log.warning(f"self._cryspy_obj.crystals: {self._cryspy_obj.crystals}") + else: + self._cryspy_obj.crystals = [*self._cryspy_obj.crystals, phase] + self._log.warning(f"self._cryspy_obj.crystals: {self._cryspy_obj.crystals}") + self._phase_names = [phase.data_name for phase in self._cryspy_obj.crystals] + self._log.debug('<---- End') + def addPhaseDefinition(self, phases_path: str) -> NoReturn: """ Add new phases from a cif file to the list of existing crystal phases in the calculator. @@ -348,67 +369,79 @@ def removePhaseDefinition(self, phase_name: str) -> NoReturn: self.disassociatePhaseFromExp(experiment.data_name, phase_name) self._log.debug('<---- End') - def writeMainCif(self, save_dir: str, filename: str = 'main.cif', exp_filename: str = 'experiments.cif', - phase_filename: str = 'phases.cif') -> NoReturn: + def blockToCif(self, block: 'pycifstar.global_.Global') -> str: + """ + Returns a cleaned up data block as text string. + + :param block: cif data block. + :return: cleaned up data block as text string. + """ + text = str(block) + text = re.sub("global_\s+", "", text) + text = re.sub("\n{3}", "\n\n", text) + return text + + def writeMainCif(self, save_dir: str, + main_filename: str = DEFAULT_FILENAMES['project'], + phase_filename: str = DEFAULT_FILENAMES['phases'], + exp_filename: str = DEFAULT_FILENAMES['experiments'], + calc_filename: str = DEFAULT_FILENAMES['calculations']) -> NoReturn: """ Write the `main.cif` where links to the experiments and phases are stored and other generalised project information. + :param save_dir: Directory to where the main cif file should be saved. + :param main_filename: What to call the main file. :param phase_filename: What to call the phases file. :param exp_filename: What to call the experiments file. - :param filename: What to call the main file. - :param save_dir: Directory to where the main cif file should be saved. + :param calc_filename: What to call the calculations file. """ self._log.debug('----> Start') - self._log.info('Writing main cif file') if not isinstance(self._cryspy_obj, cryspy.scripts.cl_rhochi.RhoChi): self._log.warning('Cryspy object is malformed. Failure...') self._log.debug('<---- End') return - main_block = self._main_rcif - save_to = os.path.join(save_dir, filename) if self._cryspy_obj.crystals is not None: - main_block["_phases"].value = phase_filename + self._project_rcif["_samples"].value = phase_filename if self._cryspy_obj.experiments is not None: - main_block["_experiments"].value = exp_filename + self._project_rcif["_experiments"].value = exp_filename + self._project_rcif["_calculations"].value = calc_filename + save_to = os.path.join(save_dir, main_filename) try: - main_block.to_file(save_to) + self._log.info('Writing main cif file') + with open(save_to, "w") as f: + f.write(self.asCifDict()["main"]) except PermissionError: self._log.warning('No permission to write to %s', save_to) except AttributeError: self._log.warning('No information stored in the object. Saving failed') self._log.debug('<---- End') - def writePhaseCif(self, save_dir: str, phase_name: str = 'phases.cif') -> NoReturn: + def writePhaseCif(self, save_dir: str, phase_name: str = DEFAULT_FILENAMES['phases']) -> NoReturn: """ - Write the `phases.cif` where all phases in the calculator are saved to file. This cif file should be + Write the `samples.cif` where all phases in the calculator are saved to file. This cif file should be compatible with other crystallographic software. :param phase_name: What to call the phases file. :param save_dir: Directory to where the phases cif file should be saved. """ self._log.debug('----> Start') - save_to = os.path.join(save_dir, phase_name) if not isinstance(self._cryspy_obj, cryspy.scripts.cl_rhochi.RhoChi): self._log.warning('Cryspy object is malformed. Failure...') self._log.debug('<---- End') return - phases_block = pycifstar.Global() - if self._cryspy_obj.crystals is not None: - self._log.info('Writing phase cif files') - phase_str = '' - for crystal in self._cryspy_obj.crystals: - phase_str += crystal.to_cif() + '\n' - phases_block.take_from_string(phase_str) - else: + save_to = os.path.join(save_dir, phase_name) + if self._cryspy_obj.crystals is None: self._log.info('No experiments to save. creating empty file: %s', save_to) try: - phases_block.to_file(save_to) + self._log.info('Writing phases cif file') + with open(save_to, "w") as f: + f.write(self.asCifDict()["phases"]) except PermissionError: self._log.warning('No permission to write to %s', save_to) self._log.debug('<---- End') - def writeExpCif(self, save_dir: str, exp_name: str = 'experiments.cif') -> NoReturn: + def writeExpCif(self, save_dir: str, exp_name: str = DEFAULT_FILENAMES['experiments']) -> NoReturn: """ Write the `experiments.cif` where all experiments in the calculator are saved to file. This includes the instrumental parameters and which phases are in the experiment/s @@ -416,41 +449,66 @@ def writeExpCif(self, save_dir: str, exp_name: str = 'experiments.cif') -> NoRet :param exp_name: What to call the experiments file. :param save_dir: Directory to where the experiment cif file should be saved. """ + self._log.debug('----> Start') if not isinstance(self._cryspy_obj, cryspy.scripts.cl_rhochi.RhoChi): self._log.warning('Cryspy object is malformed. Failure...') self._log.debug('<---- End') return save_to = os.path.join(save_dir, exp_name) - exp_block = pycifstar.Global() - if self._cryspy_obj.experiments is not None: - exp_str = '' - for experiment in self._cryspy_obj.experiments: - exp_str += experiment.to_cif() + '\n' - exp_block.take_from_string(exp_str) - else: + if self._cryspy_obj.experiments is None: self._log.info('No experiments to save. creating empty file: %s', save_to) try: - exp_block.to_file(save_to) + self._log.info('Writing experiments cif file') + with open(save_to, "w") as f: + f.write(self.asCifDict()["experiments"]) + except PermissionError: + self._log.warning('No permission to write to %s', save_to) + self._log.debug('<---- End') + + def writeCalcCif(self, save_dir: str, calc_name: str = DEFAULT_FILENAMES['calculations']) -> NoReturn: + """ + Write the `calculations.cif` where all calculations in the calculator are saved to file. + + :param calc_name: What to call the calculations file. + :param save_dir: Directory to where the calculations cif file should be saved. + """ + self._log.debug('----> Start') + if not isinstance(self._cryspy_obj, cryspy.scripts.cl_rhochi.RhoChi): + self._log.warning('Cryspy object is malformed. Failure...') + self._log.debug('<---- End') + return + save_to = os.path.join(save_dir, calc_name) + if self._cryspy_obj.experiments is None: + self._log.info('No calculations to save. creating empty file: %s', save_to) + try: + self._log.info('Writing calculations cif file') + with open(save_to, "w") as f: + f.write(self.asCifDict()["calculations"]) except PermissionError: self._log.warning('No permission to write to %s', save_to) self._log.debug('<---- End') - def saveCifs(self, save_dir: str, filename: str = 'main.cif', exp_name: str = 'experiments.cif', - phase_name: str = 'phases.cif') -> NoReturn: + def saveCifs(self, save_dir: str, + main_name: str = DEFAULT_FILENAMES['project'], + phase_name: str = DEFAULT_FILENAMES['phases'], + exp_name: str = DEFAULT_FILENAMES['experiments'], + calc_name: str = DEFAULT_FILENAMES['calculations']) -> NoReturn: """ - Write project cif files (`main.cif`, `experiments.cif` and `phases.cif`) to a user supplied directory. This - contains all information needed to recreate the calculator object. + Write project cif files (`main.cif`, `samples.cif`, `experiments.cif` and `calculations.cif`) to a user + supplied directory. This contains all information needed to recreate the project dictionary. + :param save_dir: Directory to where the main cif file should be saved. + :param main_name: What to call the main file. :param phase_name: What to call the phases file. :param exp_name: What to call the experiments file. - :param filename: What to call the main file. - :param save_dir: Directory to where the main cif file should be saved. + :param calc_name: What to call the calculations file. """ self._log.debug('----> Start') try: - self.writeMainCif(save_dir, filename) + self.writeMainCif(save_dir, main_name) self.writePhaseCif(save_dir, phase_name) self.writeExpCif(save_dir, exp_name) + self.writeCalcCif(save_dir, calc_name) self._log.info('Cifs saved successfully') except PermissionError: self._log.warning('Unable to save cifs') @@ -508,8 +566,8 @@ def _makePhase(self, calculator_phase: Crystal) -> Phase: # This is ~180ms per atom in phase. Limited by cryspy calculator_phase_name = calculator_phase.data_name # This group is < 1ms - space_group = self._getPhasesSpaceGroup(calculator_phase_name) - unit_cell = self._getPhaseCell(calculator_phase_name) + space_group = self._makePhasesSpaceGroup(calculator_phase) + unit_cell = self._makePhaseCell(calculator_phase) phase = Phase.fromPars(calculator_phase_name, space_group, unit_cell) # Atom sites ~ 6ms atoms = list(map(lambda x: self._makeAtom(calculator_phase, x), calculator_phase.atom_site.label)) @@ -635,7 +693,7 @@ def _makeAtomSites(phase: Phase, calculator_phase: Crystal) -> NoReturn: calculator_phase.atom_site.fract_z, calculator_phase.atom_site.scat_length_neutron): x_array, y_array, z_array, _ = calculator_phase.space_group.calc_xyz_mult(x.value, y.value, z.value) - scat_length_neutron_array = np.full_like(x_array, scat_length_neutron) + scat_length_neutron_array = np.full_like(x_array, scat_length_neutron, dtype=complex) atom_site_list[0] += x_array.tolist() atom_site_list[1] += y_array.tolist() atom_site_list[2] += z_array.tolist() @@ -658,36 +716,47 @@ def _makeAtomSites(phase: Phase, calculator_phase: Crystal) -> NoReturn: atom_site_list[2].append(1.0) atom_site_list[3].append(scat_length_neutron) + # convert complex numbers into strings without brackets to be recognizable in GUI + scat_length_neutron_str_array = [str(item)[1:-1] for item in atom_site_list[3]] + phase.setItemByPath(['sites', 'fract_x'], atom_site_list[0]) phase.setItemByPath(['sites', 'fract_y'], atom_site_list[1]) phase.setItemByPath(['sites', 'fract_z'], atom_site_list[2]) - phase.setItemByPath(['sites', 'scat_length_neutron'], atom_site_list[3]) + phase.setItemByPath(['sites', 'scat_length_neutron'], scat_length_neutron_str_array) def _getPhasesSpaceGroup(self, phase_name: str) -> SpaceGroup: - mapping_base = 'self._cryspy_obj.crystals' i = self._phase_names.index(phase_name) calculator_phase = self._cryspy_obj.crystals[i] + space_group = self._makePhasesSpaceGroup(calculator_phase, i) + return space_group + + def _makePhasesSpaceGroup(self, calculator_phase: Crystal, index=None) -> SpaceGroup: + mapping_base = 'self._cryspy_obj.crystals' + i = 0 + if index is not None: + i = self._phase_names.index(calculator_phase.data_name) # logging.info(calculator_phase_name) mapping_phase = mapping_base + '[{}]'.format(i) # Space group space_group = self._createProjItemFromObj(SpaceGroup.fromPars, - ['crystal_system', 'space_group_name_HM_alt', + ['crystal_system', 'space_group_name_HM_ref', 'space_group_IT_number', 'origin_choice'], [calculator_phase.space_group.crystal_system, - calculator_phase.space_group.name_hm_alt, + calculator_phase.space_group.name_hm_ref, calculator_phase.space_group.it_number, calculator_phase.space_group.it_coordinate_system_code]) space_group['crystal_system']['mapping'] = mapping_phase + '.space_group.crystal_system' - space_group['space_group_name_HM_alt']['mapping'] = mapping_phase + '.space_group.name_hm_alt' + space_group['space_group_name_HM_ref']['mapping'] = mapping_phase + '.space_group.name_hm_ref' space_group['space_group_IT_number']['mapping'] = mapping_phase + '.space_group.it_number' space_group['origin_choice']['mapping'] = mapping_phase + '.space_group.it_coordinate_system_code' return space_group - def _getPhaseCell(self, phase_name: str) -> Cell: + def _makePhaseCell(self, calculator_phase: Crystal, index=None) -> Cell: mapping_base = 'self._cryspy_obj.crystals' - i = self._phase_names.index(phase_name) - calculator_phase = self._cryspy_obj.crystals[i] + i = 0 + if index is not None: + i = self._phase_names.index(calculator_phase.data_name) # logging.info(calculator_phase_name) mapping_phase = mapping_base + '[{}]'.format(i) unit_cell = self._createProjItemFromObj(Cell.fromPars, ['length_a', 'length_b', 'length_c', @@ -705,6 +774,22 @@ def _getPhaseCell(self, phase_name: str) -> Cell: unit_cell['angle_gamma']['mapping'] = mapping_phase + '.cell.angle_gamma' return unit_cell + def _getPhaseCell(self, phase_name: str) -> Cell: + i = self._phase_names.index(phase_name) + calculator_phase = self._cryspy_obj.crystals[i] + unit_cell = self._makePhaseCell(calculator_phase, i) + return unit_cell + + def getExperimentFromCif(self, cif_string) -> Experiment: + cryspy_experiment = Pd.from_cif(cif_string) + new_exp = self._makeExperiment(cryspy_experiment) + return new_exp + + def getPhaseFromCif(self, cif_string) -> Phase: + cryspy_phase = Crystal.from_cif(cif_string) + new_phase = self._makePhase(cryspy_phase) + return new_phase + @time_it def getExperiments(self) -> Experiments: """ @@ -712,97 +797,11 @@ def getExperiments(self) -> Experiments: """ experiments = [] - mapping_base = 'self._cryspy_obj.experiments' - if self._cryspy_obj.experiments is None: return Experiments({}) for i, calculator_experiment in enumerate(self._cryspy_obj.experiments): - calculator_experiment_name = calculator_experiment.data_name - - mapping_exp = mapping_base + '[{}]'.format(i) - - # Experimental setup - calculator_setup = calculator_experiment.setup - wavelength = calculator_setup.wavelength - offset = calculator_setup.offset_ttheta - - # Scale - scale = calculator_experiment.phase.scale - - # Background - calculator_background = calculator_experiment.background - backgrounds = [] - for ii, (ttheta, intensity) in enumerate( - zip(calculator_background.ttheta, calculator_background.intensity)): - background = self._createProjItemFromObj(Background.fromPars, ['ttheta', 'intensity'], - [ttheta, intensity]) - background['intensity']['mapping'] = mapping_exp + '.background.intensity[{}]'.format(ii) - backgrounds.append(background) - backgrounds = Backgrounds(backgrounds) - - # Instrument resolution - calculator_resolution = calculator_experiment.resolution - resolution = self._createProjItemFromObj(Resolution.fromPars, - ['u', 'v', 'w', 'x', 'y'], - [calculator_resolution.u, - calculator_resolution.v, - calculator_resolution.w, - calculator_resolution.x, - calculator_resolution.y]) - resolution['u']['mapping'] = mapping_exp + '.resolution.u' - resolution['v']['mapping'] = mapping_exp + '.resolution.v' - resolution['w']['mapping'] = mapping_exp + '.resolution.w' - resolution['x']['mapping'] = mapping_exp + '.resolution.x' - resolution['y']['mapping'] = mapping_exp + '.resolution.y' - - # Measured data points - x_obs = np.array(calculator_experiment.meas.ttheta).tolist() - y_obs_up = None - sy_obs_up = None - y_obs_down = None - sy_obs_down = None - y_obs = None - sy_obs = None - if calculator_experiment.meas.intensity[0] is not None: - y_obs = np.array(calculator_experiment.meas.intensity).tolist() - sy_obs = np.array(calculator_experiment.meas.intensity_sigma).tolist() - elif calculator_experiment.meas.intensity_up[0] is not None: - y_obs_up = np.array(calculator_experiment.meas.intensity_up) - sy_obs_up = np.array(calculator_experiment.meas.intensity_up_sigma).tolist() - y_obs_down = np.array(calculator_experiment.meas.intensity_down) - sy_obs_down = np.array(calculator_experiment.meas.intensity_down_sigma).tolist() - y_obs = (y_obs_up + y_obs_down).tolist() - y_obs_up = y_obs_up.tolist() - y_obs_down = y_obs_down.tolist() - sy_obs = np.sqrt(np.square(sy_obs_up) + np.square(sy_obs_down)).tolist() - - data = MeasuredPattern(x_obs, y_obs, sy_obs, y_obs_up, sy_obs_up, y_obs_down, sy_obs_down) - - experiment = self._createProjItemFromObj(Experiment.fromPars, - ['name', 'wavelength', 'offset', 'phase', - 'background', 'resolution', 'measured_pattern'], - [calculator_experiment_name, wavelength, offset, scale[0], - backgrounds, resolution, data]) - - # Fix up phase scale, but it is a terrible way of doing things..... - phase_label = calculator_experiment.phase.label[0] - experiment['phase'][phase_label] = experiment['phase'][calculator_experiment_name] - experiment['phase'][phase_label]['scale'].refine = scale[0].refinement - experiment['phase'][phase_label]['scale']['store']['hide'] = scale[0].constraint_flag - experiment['phase'][phase_label]['name'] = phase_label - experiment['phase'][phase_label]['scale']['mapping'] = mapping_exp + '.phase.scale[0]' - del experiment['phase'][calculator_experiment_name] - if len(scale) > 1: - for idx, item in enumerate(calculator_experiment.phase.item[1:]): - experiment['phase'][item.label] = ExperimentPhase.fromPars(item.label, item.scale) - experiment['phase'][item.label]['scale']['mapping'] = mapping_exp + '.phase.scale[{}]'.format(idx) - experiment['phase'][item.label]['scale'].refine = scale[idx].refinement - experiment['phase'][item.label]['scale']['store']['hide'] = scale[idx].constraint_flag - experiment['phase'][item.label]['name'] = item.label - experiment['wavelength']['mapping'] = mapping_exp + '.setup.wavelength' - experiment['offset']['mapping'] = mapping_exp + '.setup.offset_ttheta' - + experiment = self._makeExperiment(calculator_experiment, i) experiments.append(experiment) # logging.info(experiments) @@ -856,11 +855,10 @@ def getCrystal(): if calculator_experiment.meas.intensity[0] is not None: y_obs = np.array(calculator_experiment.meas.intensity) sy_obs = np.array(calculator_experiment.meas.intensity_sigma) - ###y_calc = np.array(calculated_pattern.intensity_total) y_calc_up = np.array(calculated_pattern.intensity_up_total) - ###y_calc_down = np.array(calculated_pattern.intensity_down_total) - y_calc = y_calc_up ###+ y_calc_down - elif calculator_experiment.meas.intensity_up[0] is not None: + y_calc_down = np.array(calculated_pattern.intensity_down_total) + y_calc_bkg = np.array(calculated_pattern.intensity_bkg_calc) + if calculator_experiment.meas.intensity_up[0] is not None: y_obs_up = np.array(calculator_experiment.meas.intensity_up) sy_obs_up = np.array(calculator_experiment.meas.intensity_up_sigma) y_obs_down = np.array(calculator_experiment.meas.intensity_down) @@ -869,14 +867,16 @@ def getCrystal(): sy_obs = np.sqrt(np.square(sy_obs_up) + np.square(sy_obs_down)) y_calc_up = np.array(calculated_pattern.intensity_up_total) y_calc_down = np.array(calculated_pattern.intensity_down_total) - y_calc = y_calc_up + y_calc_down + y_calc_bkg = np.multiply(np.array(calculated_pattern.intensity_bkg_calc), 2) + y_calc = y_calc_up + y_calc_down y_obs_upper = y_obs + sy_obs y_obs_lower = y_obs - sy_obs y_diff_upper = y_obs + sy_obs - y_calc y_diff_lower = y_obs - sy_obs - y_calc limits = Limits(y_obs_lower, y_obs_upper, y_diff_upper, y_diff_lower, x_calc, y_calc) - calculated_pattern = CalculatedPattern(x_calc, y_calc, y_diff_lower, y_diff_upper) + calculated_pattern = CalculatedPattern(x_calc, y_diff_lower, y_diff_upper, y_calc_up, y_calc_down, + y_calc_bkg) calculations.append(Calculation(calculator_experiment_name, bragg_peaks, calculated_pattern, limits)) @@ -925,25 +925,32 @@ def setObjFromProjectDicts(self, phases: Phases, experiments: Experiments) -> No """Set all the cryspy parameters from project dictionary""" self.setPhases(phases) self.setExperiments(experiments) + cif = self._cryspy_obj.to_cif() + self._cryspy_obj = RhoChi().from_cif(cif) def asCifDict(self) -> dict: - """...""" - experiments = {} - calculations = {} - phases = {} + """Returns dict of all the CIFs""" + main = "" + phases = "" + experiments = "" + calculations = "" if isinstance(self._cryspy_obj, cryspy.scripts.cl_rhochi.RhoChi): - if self._cryspy_obj.experiments is not None: - experiments = "data_" + self._cryspy_obj.experiments[0].data_name + "\n\n" + \ - self._cryspy_obj.experiments[0].params_to_cif() + "\n" + self._cryspy_obj.experiments[ - 0].data_to_cif() # temporarily solution, as params_to_cif, data_to_cif and calc_to_cif are not implemented yet in cryspy 0.2.0 - calculations = self._cryspy_obj.experiments[0].calc_to_cif() + # main.cif + if self._project_rcif is not None: + main = self.blockToCif(self._project_rcif) + # samples.cif if self._cryspy_obj.crystals is not None: - phases = self._cryspy_obj.crystals[0].to_cif() - return { - 'phases': phases, - 'experiments': experiments, - 'calculations': calculations - } + for phase in self._cryspy_obj.crystals: + phases += phase.to_cif() + '\n' + # experiments.cif & calculations.cif + if self._cryspy_obj.experiments is not None: + for experiment in self._cryspy_obj.experiments: + experiments += "data_" + experiment.data_name + "\n\n" + \ + experiment.params_to_cif() + "\n" + \ + experiment.data_to_cif() + calculations += "data_" + experiment.data_name + "\n\n" + \ + experiment.calc_to_cif() + return { 'main': main, 'phases': phases, 'experiments': experiments, 'calculations': calculations } @time_it def refine(self) -> Tuple[dict, dict]: @@ -978,13 +985,27 @@ def _mappedRefineUpdater(self, item_str, value) -> NoReturn: item = aeval(item_str) item.refinement = value + # TODO this section needs to be modified. Main rcif needs to be moved to interface and this implementation removed def getProjectName(self) -> str: try: - name = self._main_rcif["name"].value + name = self._project_rcif["name"].value + except TypeError: + name = '' + return name + + def setProjectName(self, value: str) -> NoReturn: + self._project_rcif["name"].value = value + + def getProjectKeywords(self) -> str: + try: + name = self._project_rcif["keywords"].value except TypeError: name = '' return name + def setProjectKeywords(self, value: str) -> NoReturn: + self._project_rcif["keywords"].value = value + def getPhaseNames(self) -> list: return self._phase_names @@ -1009,112 +1030,172 @@ def addExperiment(self, experiment: Experiment) -> NoReturn: @staticmethod def _createPhaseObj(phase: Phase) -> Crystal: - this_cell = cryspyCell(length_a=phase['cell']['length_a'].value, - length_b=phase['cell']['length_b'].value, - length_c=phase['cell']['length_c'].value, - angle_alpha=phase['cell']['angle_alpha'].value, - angle_beta=phase['cell']['angle_beta'].value, - angle_gamma=phase['cell']['angle_gamma'].value) - - this_space_group = cpSpaceGroup(name_hm_alt=phase['spacegroup']['space_group_name_HM_alt'].value, - it_number=phase['spacegroup']['space_group_IT_number'].value, - it_coordinate_system_code=phase['spacegroup']['origin_choice'].value, - crystal_system=phase['spacegroup']['crystal_system'].value) + + def convRefine(cp_in, easy_in, cp_keys, e_keys): + for cp_param, e_param in zip(cp_keys, e_keys): + if isinstance(easy_in[e_param], Base): + cp_obj = getattr(cp_in, cp_param) + if isinstance(cp_obj, Fitable): + setattr(cp_obj, 'refinement', easy_in[e_param].refine) + + d = dict() + d['data_name'] = phase['phasename'] + cell = dict() + cell['length_a'] = phase['cell']['length_a'].value + cell['length_b'] = phase['cell']['length_b'].value + cell['length_c'] = phase['cell']['length_c'].value + cell['angle_alpha'] = phase['cell']['angle_alpha'].value + cell['angle_beta'] = phase['cell']['angle_beta'].value + cell['angle_gamma'] = phase['cell']['angle_gamma'].value + d['cell'] = cryspyCell(**cell) + convRefine(d['cell'], phase['cell'], + ['length_a', 'length_b', 'length_c', 'angle_alpha', 'angle_beta', 'angle_gamma'], + ['length_a', 'length_b', 'length_c', 'angle_alpha', 'angle_beta', 'angle_gamma']) + + spg = dict() + # spg['name_hm_ref'] = phase['spacegroup']['space_group_name_HM_ref'].value + spg['it_number'] = phase['spacegroup']['space_group_IT_number'].value + spg['it_coordinate_system_code'] = phase['spacegroup']['origin_choice'].value + # spg['crystal_system'] = phase['spacegroup']['crystal_system'].value + d['space_group'] = cpSpaceGroup(**spg) + convRefine(d['space_group'], phase['spacegroup'], + ['it_coordinate_system_code', 'it_number'], + ['origin_choice', 'space_group_IT_number']) this_atoms = [] this_adp = [] this_msp = [] - for atomLabel in phase['atoms'].keys(): - atom = phase['atoms'][atomLabel] - this_atoms.append( - AtomSite(label=atomLabel, type_symbol=atom['type_symbol'], - fract_x=atom['fract_x'].value, fract_y=atom['fract_y'].value, - fract_z=atom['fract_z'].value, - occupancy=atom['occupancy'].value, adp_type=atom['adp_type'].value, - u_iso_or_equiv=atom['U_iso_or_equiv'].value) - ) + for atom_label in phase['atoms'].keys(): + atom = phase['atoms'][atom_label] + atom_site = dict() + atom_site['label'] = atom_label + atom_site['type_symbol'] = atom['type_symbol'] + atom_site['fract_x'] = atom['fract_x'].value + atom_site['fract_y'] = atom['fract_y'].value + atom_site['fract_z'] = atom['fract_z'].value + atom_site['occupancy'] = atom['occupancy'].value + atom_site['adp_type'] = atom['adp_type'].value + atom_site['u_iso_or_equiv'] = atom['U_iso_or_equiv'].value + a_site = AtomSite(**atom_site) + convRefine(a_site, atom, + ['type_symbol', 'fract_x', 'fract_y', 'fract_z', 'occupancy', 'adp_type', 'u_iso_or_equiv'], + ['type_symbol', 'fract_x', 'fract_y', 'fract_z', 'occupancy', 'adp_type', 'U_iso_or_equiv']) + this_atoms.append(a_site) if atom['ADP']['u_11'].value is not None: - this_adp.append( - AtomSiteAniso(label=atomLabel, u_11=atom['ADP']['u_11'].value, - u_22=atom['ADP']['u_22'].value, u_33=atom['ADP']['u_33'].value, - u_12=atom['ADP']['u_12'].value, u_13=atom['ADP']['u_13'].value, - u_23=atom['ADP']['u_23'].value) - ) + adp = dict() + adp['label'] = atom_label + adp['u_11'] = atom['ADP']['u_11'].value + adp['u_22'] = atom['ADP']['u_22'].value + adp['u_33'] = atom['ADP']['u_33'].value + adp['u_12'] = atom['ADP']['u_12'].value + adp['u_13'] = atom['ADP']['u_13'].value + adp['u_23'] = atom['ADP']['u_23'].value + adp = AtomSiteAniso(**adp) + convRefine(adp, atom['ADP'], + ['u_11', 'u_22', 'u_33', 'u_12', 'u_13', 'u_23'], + ['u_11', 'u_22', 'u_33', 'u_12', 'u_13', 'u_23']) + this_adp.append(adp) if atom['MSP']['type'].value is not None: - this_msp.append( - AtomSiteSusceptibility(label=atomLabel, chi_type=atom['MSP']['type'].value, - chi_11=atom['MSP']['chi_11'].value, - chi_22=atom['MSP']['chi_22'].value, chi_33=atom['MSP']['chi_33'].value, - chi_12=atom['MSP']['chi_12'].value, chi_13=atom['MSP']['chi_13'].value, - chi_23=atom['MSP']['chi_23'].value) - ) - - this_atoms = AtomSiteL(this_atoms) - this_adp = AtomSiteAnisoL(this_adp) - this_msp = AtomSiteSusceptibilityL(this_msp) - - phase_obj = Crystal(data_name=phase['phasename'], cell=this_cell, space_group=this_space_group, - atom_site=this_atoms, - atom_site_aniso=this_adp, atom_site_susceptibility=this_msp) + msp = dict() + msp['label'] = atom_label + msp['chi_type'] = atom['MSP']['type'].value + msp['chi_11'] = atom['MSP']['chi_11'].value + msp['chi_22'] = atom['MSP']['chi_22'].value + msp['chi_33'] = atom['MSP']['chi_33'].value + msp['chi_12'] = atom['MSP']['chi_12'].value + msp['chi_13'] = atom['MSP']['chi_13'].value + msp['chi_23'] = atom['MSP']['chi_23'].value + msp = AtomSiteSusceptibility(**msp) + convRefine(msp, atom['MSP'], + ['chi_11', 'chi_22', 'chi_33', 'chi_12', 'chi_13', 'chi_23'], + ['chi_11', 'chi_22', 'chi_33', 'chi_12', 'chi_13', 'chi_23']) + this_msp.append(msp) + + d['atom_site'] = AtomSiteL(this_atoms) + if len(this_adp) > 0: + d['atom_site_aniso'] = AtomSiteAnisoL(this_adp) + if len(this_msp) > 0: + d['atom_site_susceptibility'] = AtomSiteSusceptibilityL(this_msp) + # This means that it's magnetic, so it must have a atom_site_scat + d['atom_site_scat'] = AtomSiteScatL([AtomSiteScat(label=msp.label) for msp in this_msp]) + + phase_obj = Crystal(**d) phase_obj.apply_constraint() return phase_obj - @staticmethod - def _createExperimentObj(experiment: Experiment) -> Pd: + def _createExperimentObj(self, experiment: Experiment) -> Pd: # First create a background - backgrounds = PdBackgroundL( - list( - map( - lambda key: PdBackground(ttheta=experiment['background'][key]['ttheta'], - intensity=experiment['background'][key]['intensity'].value), - experiment['background'].keys() - ) - ) + + exp = dict() + exp['data_name'] = experiment['name'] + + def bg_mapper(key): + bg = PdBackground(ttheta=experiment['background'][key]['ttheta'], + intensity=experiment['background'][key]['intensity'].value) + bg.intensity.refinement = experiment['background'][key]['intensity'].refine + return bg + + bg_keys = [float(key) for key in experiment['background'].keys()] + bg_keys.sort() + exp['background'] = PdBackgroundL( + list(map(lambda key: bg_mapper(str(key)), bg_keys)) ) # backgrounds = PdBackgroundL(backgrounds) # Resolution - resolution = PdInstrResolution(u=experiment['resolution']['u'].value, - v=experiment['resolution']['v'].value, - w=experiment['resolution']['w'].value, - x=experiment['resolution']['x'].value, - y=experiment['resolution']['y'].value) + exp['resolution'] = PdInstrResolution(u=experiment['resolution']['u'].value, + v=experiment['resolution']['v'].value, + w=experiment['resolution']['w'].value, + x=experiment['resolution']['x'].value, + y=experiment['resolution']['y'].value) + exp['resolution'].u.refinement = experiment['resolution']['u'].refine + exp['resolution'].v.refinement = experiment['resolution']['v'].refine + exp['resolution'].w.refinement = experiment['resolution']['w'].refine + exp['resolution'].x.refinement = experiment['resolution']['x'].refine + exp['resolution'].y.refinement = experiment['resolution']['y'].refine # Measured pattern - pol = lambda data: PdMeas(ttheta=data[0], intensity=data[1], intensity_sigma=data[2], - intensity_up=data[3], intensity_up_sigma=data[4], - intensity_down=data[5], intensity_down_sigma=data[6]) + pol = lambda data: PdMeas(ttheta=data[0], + intensity_up=data[1], intensity_up_sigma=data[2], + intensity_down=data[3], intensity_down_sigma=data[4]) non_pol = lambda data: PdMeas(ttheta=data[0], intensity=data[1], intensity_sigma=data[2]) if experiment['measured_pattern'].isPolarised: pattern = PdMeasL(list(map(pol, zip(experiment['measured_pattern']['x'], - experiment['measured_pattern']['y_obs'], - experiment['measured_pattern']['sy_obs'], experiment['measured_pattern']['y_obs_up'], experiment['measured_pattern']['sy_obs_up'], experiment['measured_pattern']['y_obs_down'], experiment['measured_pattern']['sy_obs_down'])))) + exp['chi2'] = Chi2(sum=experiment['refinement_type'].sum, diff=experiment['refinement_type'].diff, up=False, down=False) + exp['diffrn_radiation'] = DiffrnRadiation(polarization=experiment['polarization']['polarization'].value, + efficiency=experiment['polarization']['efficiency'].value) + exp['diffrn_radiation'].polarization.refinement = experiment['polarization']['polarization'].refine + exp['diffrn_radiation'].efficiency.refinement = experiment['polarization']['efficiency'].refine else: pattern = PdMeasL(list(map(non_pol, zip(experiment['measured_pattern']['x'], experiment['measured_pattern']['y_obs'], experiment['measured_pattern']['sy_obs'])))) - # Associate it to a phase - phases = PhaseL( - list( - map( - lambda key: cryspyPhase(label=key, scale=experiment['phase'][key]['scale'].value, igsize=0), - experiment['phase'].keys() - ) - ) - ) + exp['meas'] = pattern + def phase_mapper(key): + phase = cryspyPhase(label=key, scale=experiment['phase'][key]['scale'].value, igsize=0) + phase.scale.refinement = experiment['phase'][key]['scale'].refine + return phase + + # Associate it to a phase + exp['phase'] = PhaseL([phase_mapper(key) for key in experiment['phase'].keys()]) # Setup the instrument... - instrument = Setup(wavelength=experiment['wavelength'].value, offset_ttheta=experiment['offset'].value) + if experiment['measured_pattern'].isPolarised: + exp['setup'] = Setup(wavelength=experiment['wavelength'].value, offset_ttheta=experiment['offset'].value, + field=experiment['magnetic_field'].value) - return Pd(data_name=experiment['name'], background=backgrounds, resolution=resolution, meas=pattern, - phase=phases, setup=instrument) + else: + exp['setup'] = Setup(wavelength=experiment['wavelength'].value, offset_ttheta=experiment['offset'].value) + exp['setup'].wavelength.refinement = experiment['wavelength'].refine + exp['setup'].offset_ttheta.refinement = experiment['offset'].refine + return Pd(**exp) def associatePhaseToExp(self, exp_name: str, phase_name: str, scale: float, igsize: float = 0.0) -> NoReturn: cryspyPhaseObj = cryspyPhase(label=phase_name, scale=scale, igsize=igsize) @@ -1137,3 +1218,148 @@ def getPhasesAssocatedToExp(self, exp_name: str) -> list: # THIS IS A HACK AS CRYSPY IS SGSHRGFHGHRTGYHT exp_phases = [item[0] for item in phases.items] return exp_phases + + def _makeExperiment(self, calculator_experiment, i=0) -> Experiment: + + mapping_base = 'self._cryspy_obj.experiments' + calculator_experiment_name = calculator_experiment.data_name + mapping_exp = mapping_base + '[{}]'.format(i) + + # Experimental setup + calculator_setup = calculator_experiment.setup + wavelength = calculator_setup.wavelength + offset = calculator_setup.offset_ttheta + + is_polarised = hasattr(calculator_setup, 'field') + magnetic_field = None + if is_polarised: + magnetic_field = calculator_setup.field + chi2 = {'sum': True, 'diff': False, 'up': False, 'down': False} + rad = {'polarization': 0, 'efficiency': 1} + for obj in calculator_experiment.optional_objs: + if isinstance(obj, Chi2): + for key in chi2.keys(): + chi2[key] = getattr(obj, key) + elif isinstance(obj, DiffrnRadiation): + for key in rad.keys(): + rad[key] = getattr(obj, key) + # Scale + scale = calculator_experiment.phase.scale + + # Background + calculator_background = calculator_experiment.background + backgrounds = [] + for ii, (ttheta, intensity) in enumerate( + zip(calculator_background.ttheta, calculator_background.intensity)): + background = self._createProjItemFromObj(Background.fromPars, ['ttheta', 'intensity'], + [ttheta, intensity]) + background['intensity']['mapping'] = mapping_exp + '.background.intensity[{}]'.format(ii) + backgrounds.append(background) + backgrounds.sort(key=lambda x: float(x['ttheta'])) + backgrounds = Backgrounds(backgrounds) + + # Instrument resolution + calculator_resolution = calculator_experiment.resolution + resolution = self._createProjItemFromObj(Resolution.fromPars, + ['u', 'v', 'w', 'x', 'y'], + [calculator_resolution.u, + calculator_resolution.v, + calculator_resolution.w, + calculator_resolution.x, + calculator_resolution.y]) + resolution['u']['mapping'] = mapping_exp + '.resolution.u' + resolution['v']['mapping'] = mapping_exp + '.resolution.v' + resolution['w']['mapping'] = mapping_exp + '.resolution.w' + resolution['x']['mapping'] = mapping_exp + '.resolution.x' + resolution['y']['mapping'] = mapping_exp + '.resolution.y' + + # Measured data points + x_obs = np.array(calculator_experiment.meas.ttheta).tolist() + y_obs_up = None + sy_obs_up = None + y_obs_diff = None + sy_obs_diff = None + y_obs_down = None + sy_obs_down = None + y_obs = None + sy_obs = None + if calculator_experiment.meas.intensity[0] is not None: + y_obs = np.array(calculator_experiment.meas.intensity).tolist() + sy_obs = np.array(calculator_experiment.meas.intensity_sigma).tolist() + elif calculator_experiment.meas.intensity_up[0] is not None: + y_obs_up = np.array(calculator_experiment.meas.intensity_up) + sy_obs_up = np.array(calculator_experiment.meas.intensity_up_sigma).tolist() + y_obs_down = np.array(calculator_experiment.meas.intensity_down) + sy_obs_down = np.array(calculator_experiment.meas.intensity_down_sigma).tolist() + y_obs = (y_obs_up + y_obs_down).tolist() + y_obs_diff = (y_obs_up - y_obs_down).tolist() + sy_obs_diff = np.sqrt(np.square(sy_obs_up) + np.square(sy_obs_down)).tolist() + y_obs_up = y_obs_up.tolist() + y_obs_down = y_obs_down.tolist() + sy_obs = np.sqrt(np.square(sy_obs_up) + np.square(sy_obs_down)).tolist() + + data = MeasuredPattern(x_obs, y_obs, sy_obs, y_obs_diff, sy_obs_diff, y_obs_up, sy_obs_up, y_obs_down, + sy_obs_down) + + experiment = self._createProjItemFromObj(Experiment.fromPars, + ['name', 'wavelength', 'offset', 'phase', + 'background', 'resolution', 'measured_pattern', 'magnetic_field'], + [calculator_experiment_name, wavelength, offset, scale[0], + backgrounds, resolution, data, magnetic_field]) + + if data.isPolarised: + options = ['sum', 'diff'] + experiment['refinement_type'].set_object(calculator_experiment.chi2) + for option in options: + if getattr(calculator_experiment.chi2, option): + setattr(experiment['refinement_type'], option, True) + experiment['polarization'][ + 'polarization'].value = calculator_experiment.diffrn_radiation.polarization.value + experiment['polarization']['polarization']['store'][ + 'error'] = calculator_experiment.diffrn_radiation.polarization.sigma + + experiment['polarization'][ + 'polarization'].refine = calculator_experiment.diffrn_radiation.polarization.refinement + experiment['polarization']['polarization']['store']['hide'] = False + experiment['polarization']['polarization']['mapping'] = mapping_exp + '.diffrn_radiation.polarization' + experiment['polarization']['efficiency'].value = calculator_experiment.diffrn_radiation.efficiency.value + experiment['polarization'][ + 'efficiency'].refine = calculator_experiment.diffrn_radiation.efficiency.refinement + experiment['polarization']['efficiency']['store'][ + 'error'] = calculator_experiment.diffrn_radiation.efficiency.sigma + experiment['polarization']['efficiency']['store']['hide'] = False + experiment['polarization']['efficiency']['mapping'] = mapping_exp + '.diffrn_radiation.efficiency' + + # updateMinMax method is called only when polarization object is created in + # Experiment.py (Line 430): polarization=Polarization.default(). The default values + # of polarization.polarization and polarization.efficiency = 1.0 at that moment, so + # min and max are defined as 0.8 and 1.2, respectively. + # Now, we need to reset min and max and call updateMinMax() again! + experiment['polarization']['polarization'].min = -np.Inf + experiment['polarization']['polarization'].max = np.Inf + experiment['polarization']['polarization'].updateMinMax() + experiment['polarization']['efficiency'].min = -np.Inf + experiment['polarization']['efficiency'].max = np.Inf + experiment['polarization']['efficiency'].updateMinMax() + + # Fix up phase scale, but it is a terrible way of doing things..... + phase_label = calculator_experiment.phase.label[0] + experiment['phase'][phase_label] = experiment['phase'][calculator_experiment_name] + experiment['phase'][phase_label]['scale'].refine = scale[0].refinement + experiment['phase'][phase_label]['scale']['store']['hide'] = scale[0].constraint_flag + experiment['phase'][phase_label]['name'] = phase_label + experiment['phase'][phase_label]['scale']['mapping'] = mapping_exp + '.phase.scale[0]' + del experiment['phase'][calculator_experiment_name] + if len(scale) > 0: + for idx, item in enumerate(calculator_experiment.phase.item[0:]): + experiment['phase'][item.label] = ExperimentPhase.fromPars(item.label, scale[idx].value) + experiment['phase'][item.label]['scale']['mapping'] = mapping_exp + '.phase.scale[{}]'.format(idx) + experiment['phase'][item.label]['scale'].refine = scale[idx].refinement + experiment['phase'][item.label]['scale']['store']['hide'] = scale[idx].constraint_flag + experiment['phase'][item.label]['scale']['store']['error'] = scale[idx].sigma + experiment['phase'][item.label]['name'] = item.label + experiment['wavelength']['mapping'] = mapping_exp + '.setup.wavelength' + experiment['offset']['mapping'] = mapping_exp + '.setup.offset_ttheta' + experiment['magnetic_field']['mapping'] = mapping_exp + '.setup.field' + + return experiment diff --git a/easyInterface/Diffraction/DataClasses/DataObj/Calculation.py b/easyInterface/Diffraction/DataClasses/DataObj/Calculation.py index 3130350..b451d80 100755 --- a/easyInterface/Diffraction/DataClasses/DataObj/Calculation.py +++ b/easyInterface/Diffraction/DataClasses/DataObj/Calculation.py @@ -73,8 +73,15 @@ class CalculatedPattern(LoggedPathDict): """ Storage container for a calculated pattern """ - def __init__(self, x: list, y_calc: list, y_diff_lower: list, y_diff_upper: list): - super().__init__(x=x, y_calc=y_calc, y_diff_lower=y_diff_lower, y_diff_upper=y_diff_upper) + def __init__(self, x: list, y_diff_lower: list, y_diff_upper: list, y_calc_up: list, y_calc_down: list = [], y_calc_bkg: list = []): + y_calc_down_temp = y_calc_down + if len(y_calc_down) == 0: + y_calc_down_temp = [0]*len(y_calc_up) + + super().__init__(x=x, y_calc_sum=np.array(y_calc_up) + np.array(y_calc_down_temp), y_calc_diff=np.array(y_calc_up) - np.array(y_calc_down_temp), + y_calc_up=y_calc_up, y_calc_down=y_calc_down, + y_diff_lower=y_diff_lower, y_diff_upper=y_diff_upper, + y_calc_bkg=y_calc_bkg) self._log = logging.getLogger(__class__.__module__) def __repr__(self): @@ -92,17 +99,17 @@ def __init__(self, name: str, bragg_peaks: BraggPeaks, calculated_pattern: Calcu @classmethod def default(cls, name: str): bragg_peaks = BraggPeaks({}) - calculated_pattern = CalculatedPattern([0], [0], [0], [0]) + calculated_pattern = CalculatedPattern([0], [0], [0], [0],[0]) limits = Limits() return cls(name, bragg_peaks, calculated_pattern, limits) @classmethod def fromPars(cls, name: str, bragg_crystals: CrystalBraggPeaks, - y_obs_lower: list, y_obs_upper: list, - tth: list, y_calc: list, y_diff_lower: list, y_diff_upper: list): + y_calc_lower: list, y_calc_upper: list, + tth: list, y_calc: list, y_diff_lower: list, y_diff_upper: list, y_calc_bkg: list): bragg_peaks = BraggPeaks(bragg_crystals) - calculated_pattern = CalculatedPattern(tth, y_calc, y_diff_lower, y_diff_upper) - limits = Limits(y_obs_lower, y_obs_upper, y_diff_upper, y_diff_lower, x_calc=tth, y_calc=y_calc) + calculated_pattern = CalculatedPattern(tth, y_calc, y_diff_lower, y_diff_upper, y_calc_bkg) + limits = Limits(y_calc_lower, y_calc_upper, y_diff_upper, y_diff_lower, x_calc=tth, y_calc=y_calc) return cls(name, bragg_peaks, calculated_pattern, limits) def __repr__(self): diff --git a/easyInterface/Diffraction/DataClasses/DataObj/Experiment.py b/easyInterface/Diffraction/DataClasses/DataObj/Experiment.py index 43a5e6d..238386d 100644 --- a/easyInterface/Diffraction/DataClasses/DataObj/Experiment.py +++ b/easyInterface/Diffraction/DataClasses/DataObj/Experiment.py @@ -18,6 +18,12 @@ 'tooltip': 'Degree offset in two theta', 'url': '', 'default': (0, 'deg') + }, + 'magnetic_field': { + 'header': 'Field', + 'tooltip': 'Applied magnetic field', + 'url': '', + 'default': (0, 'T') } } @@ -48,11 +54,27 @@ } } +POLARIZATION_DETAILS = { + 'polarization': { + 'header': '', + 'tooltip': '', + 'url': '', + 'default': (1, '') + }, + 'efficiency': { + 'header': '', + 'tooltip': '', + 'url': '', + 'default': (1, '') + } +} + class Resolution(LoggedPathDict): """ Data store for the resolution parameters """ + def __init__(self, u: Base, v: Base, w: Base, x: Base, y: Base): """ Dictionary store for resolution parameters @@ -128,6 +150,7 @@ class Background(LoggedPathDict): """ Data store for the background data parameters """ + def __init__(self, ttheta: float, intensity: Base): """ Background dictionary @@ -173,6 +196,7 @@ class Backgrounds(ContainerObj): """ Store for a collection of background points """ + def __init__(self, backgrounds: Union[Background, dict, list]): """ Constructor for Background data points @@ -185,14 +209,24 @@ def __init__(self, backgrounds: Union[Background, dict, list]): def __repr__(self): return '{} Backgrounds'.format(len(self)) + def sort(self, reverse=False): + keys = list(self.keys()) + keys.sort(key=float, reverse=reverse) + new_bg = [] + for key in keys: + new_bg.append(self[key]) + super().__init__(new_bg, Background) + class MeasuredPattern(LoggedPathDict): """ Storage container for measured patterns """ - def __init__(self, x: list, y_obs: list, sy_obs: list, y_obs_up: Union[list, None] = None, - sy_obs_up: Union[list, None] = None, y_obs_down: Union[list, None] = None, - sy_obs_down: Union[list, None] = None): + + def __init__(self, x: list, y_obs: list, sy_obs: list, + y_obs_diff: Union[list, None] = None, sy_obs_diff: Union[list, None] = None, + y_obs_up: Union[list, None] = None, sy_obs_up: Union[list, None] = None, + y_obs_down: Union[list, None] = None, sy_obs_down: Union[list, None] = None): """ Constructor for a measured pattern @@ -206,7 +240,8 @@ def __init__(self, x: list, y_obs: list, sy_obs: list, y_obs_up: Union[list, Non """ # 1d polarised powder diffraction data # if y_obs_up is not None and sy_obs_up is not None and y_obs_down is not None and sy_obs_down is not None: - super().__init__(x=x, y_obs=y_obs, sy_obs=sy_obs, y_obs_up=y_obs_up, sy_obs_up=sy_obs_up, + super().__init__(x=x, y_obs=y_obs, sy_obs=sy_obs, y_obs_diff=y_obs_diff, sy_obs_diff=sy_obs_diff, + y_obs_up=y_obs_up, sy_obs_up=sy_obs_up, y_obs_down=y_obs_down, sy_obs_down=sy_obs_down) self._log = logging.getLogger(__class__.__module__) # # 1d unpolarised powder diffraction data @@ -254,22 +289,27 @@ def default(cls, polarised: bool = False): x = [] y_obs = [] sy_obs = [] + y_obs_diff = None + sy_obs_diff = None y_obs_up = None sy_obs_up = None y_obs_down = None sy_obs_down = None if polarised: + y_obs_diff = [] + sy_obs_diff = [] y_obs_up = [] sy_obs_up = [] y_obs_down = [] sy_obs_down = [] - return cls(x, y_obs, sy_obs, y_obs_up, sy_obs_up, y_obs_down, sy_obs_down) + return cls(x, y_obs, sy_obs, y_obs_diff, sy_obs_diff, y_obs_up, sy_obs_up, y_obs_down, sy_obs_down) class ExperimentPhase(LoggedPathDict): """ Storage container for the Experimental Phase details """ + def __init__(self, name: str, scale: Base): """ Constructor for the Experimental phase container @@ -278,9 +318,11 @@ def __init__(self, name: str, scale: Base): """ super().__init__(name=name, scale=scale) self._log = logging.getLogger(__class__.__module__) + self.setItemByPath(['scale', 'header'], SCALE_DETAILS['scale']['header']) self.setItemByPath(['scale', 'tooltip'], SCALE_DETAILS['scale']['tooltip']) self.setItemByPath(['scale', 'url'], SCALE_DETAILS['scale']['url']) + self._log.debug('Created phase: {}'.format(self)) @classmethod @@ -311,6 +353,7 @@ class ExperimentPhases(ContainerObj): """ Storage of multiple phase markers associated with experiments """ + def __init__(self, experiment_phases: Union[list, ExperimentPhase, dict]): """ Constructor for holding multiple experiments @@ -324,11 +367,69 @@ def __repr__(self) -> str: return '{} Experimental phases'.format(len(self)) +class RefinementType(LoggedPathDict): + _default = {'_sum': False, '_diff': False} + + def __init__(self): + super().__init__(**self._default) + self.object = None + + def set_object(self, obj): + self.object = obj + + @property + def sum(self): + return self['_sum'] + + @sum.setter + def sum(self, value): + if self.object is not None: + self.object.sum = value + self['_sum'] = value + + @property + def diff(self): + return self['_diff'] + + @diff.setter + def diff(self, value): + if self.object is not None: + self.object.diff = value + self['_diff'] = value + + +class Polarization(LoggedPathDict): + def __init__(self, polarization: Base, efficiency: Base): + super().__init__(polarization=polarization, efficiency=efficiency) + + self.setItemByPath(['polarization', 'header'], POLARIZATION_DETAILS['polarization']['header']) + self.setItemByPath(['polarization', 'tooltip'], POLARIZATION_DETAILS['polarization']['tooltip']) + self.setItemByPath(['polarization', 'url'], POLARIZATION_DETAILS['polarization']['url']) + + self.setItemByPath(['efficiency', 'header'], POLARIZATION_DETAILS['efficiency']['header']) + self.setItemByPath(['efficiency', 'tooltip'], POLARIZATION_DETAILS['efficiency']['tooltip']) + self.setItemByPath(['efficiency', 'url'], POLARIZATION_DETAILS['efficiency']['url']) + + @classmethod + def default(cls): + polarization = Base(*POLARIZATION_DETAILS['polarization']['default']) + efficiency = Base(*POLARIZATION_DETAILS['efficiency']['default']) + return cls(polarization, efficiency) + + @classmethod + def fromPars(cls, polarization, efficiency): + polarization = Base(polarization, POLARIZATION_DETAILS['polarization']['default'][1]) + efficiency = Base(efficiency, POLARIZATION_DETAILS['efficiency']['default'][1]) + return cls(polarization, efficiency) + + class Experiment(LoggedPathDict): """ Experimental details data container """ - def __init__(self, name: str, wavelength: Base, offset: Base, phase: ExperimentPhases, background: Backgrounds, + + def __init__(self, name: str, wavelength: Base, offset: Base, magnetic_field: Base, phase: ExperimentPhases, + background: Backgrounds, resolution: Resolution, measured_pattern: MeasuredPattern): """ Constructor for experimental data container @@ -341,8 +442,14 @@ def __init__(self, name: str, wavelength: Base, offset: Base, phase: ExperimentP :param resolution: Description of the resolution :param measured_pattern: What was actually measured """ - super().__init__(name=name, wavelength=wavelength, offset=offset, phase=phase, background=background, - resolution=resolution, measured_pattern=measured_pattern) + + refinement_type = RefinementType() + refinement_type.sum = True + + super().__init__(name=name, wavelength=wavelength, offset=offset, magnetic_field=magnetic_field, phase=phase, + background=background, + resolution=resolution, measured_pattern=measured_pattern, refinement_type=refinement_type, + polarization=Polarization.default()) self._log = logging.getLogger(__class__.__module__) self.setItemByPath(['wavelength', 'header'], EXPERIMENT_DETAILS['wavelength']['header']) @@ -353,6 +460,10 @@ def __init__(self, name: str, wavelength: Base, offset: Base, phase: ExperimentP self.setItemByPath(['offset', 'tooltip'], EXPERIMENT_DETAILS['offset']['tooltip']) self.setItemByPath(['offset', 'url'], EXPERIMENT_DETAILS['offset']['url']) + self.setItemByPath(['magnetic_field', 'header'], EXPERIMENT_DETAILS['magnetic_field']['header']) + self.setItemByPath(['magnetic_field', 'tooltip'], EXPERIMENT_DETAILS['magnetic_field']['tooltip']) + self.setItemByPath(['magnetic_field', 'url'], EXPERIMENT_DETAILS['magnetic_field']['url']) + @classmethod def default(cls, name: str) -> 'Experiment': """ @@ -363,15 +474,16 @@ def default(cls, name: str) -> 'Experiment': """ wavelength = Base(*EXPERIMENT_DETAILS['wavelength']['default']) offset = Base(*EXPERIMENT_DETAILS['offset']['default']) + field = Base(*EXPERIMENT_DETAILS['magnetic_field']['default']) phase = ExperimentPhases({}) backgrounds = Backgrounds(Background.default()) resolution = Resolution.default() measured_pattern = MeasuredPattern.default() - return cls(name, wavelength, offset, phase, backgrounds, resolution, measured_pattern) + return cls(name, wavelength, offset, field, phase, backgrounds, resolution, measured_pattern) @classmethod def fromPars(cls, name: str, wavelength: float, offset: float, scale: float, background: Backgrounds, - resolution: Resolution, measured_pattern: MeasuredPattern) -> 'Experiment': + resolution: Resolution, measured_pattern: MeasuredPattern, magnetic_field: float = None) -> 'Experiment': """ Constructor of experiment from parameters @@ -386,8 +498,12 @@ def fromPars(cls, name: str, wavelength: float, offset: float, scale: float, bac """ wavelength = Base(wavelength, EXPERIMENT_DETAILS['wavelength']['default'][1]) offset = Base(offset, EXPERIMENT_DETAILS['offset']['default'][1]) + if magnetic_field is None: + magnetic_field = Base(*EXPERIMENT_DETAILS['magnetic_field']['default']) + else: + magnetic_field = Base(magnetic_field, EXPERIMENT_DETAILS['magnetic_field']['default'][1]) phase = ExperimentPhases(ExperimentPhase.fromPars(name, scale)) - return cls(name, wavelength, offset, phase, background, resolution, measured_pattern) + return cls(name, wavelength, offset, magnetic_field, phase, background, resolution, measured_pattern) def __repr__(self): return 'Experiment: {}'.format(self['name']) @@ -397,6 +513,7 @@ class Experiments(ContainerObj): """ Container for multiple experiments """ + def __init__(self, experiments: Union[Experiment, dict, list]): """ Constructor for holding multiple experiments diff --git a/easyInterface/Diffraction/DataClasses/PhaseObj/Atom.py b/easyInterface/Diffraction/DataClasses/PhaseObj/Atom.py index 0c5ace8..8bfca6b 100755 --- a/easyInterface/Diffraction/DataClasses/PhaseObj/Atom.py +++ b/easyInterface/Diffraction/DataClasses/PhaseObj/Atom.py @@ -169,7 +169,7 @@ def fromXYZ(cls, atom_site_label: str, type_symbol: str, x: float, y: float, z: return obj @classmethod - def fromPars(cls, atom_site_label: str, type_symbol: str, scat_length_neutron: float, + def fromPars(cls, atom_site_label: str, type_symbol: str, scat_length_neutron: complex, fract_x: float, fract_y: float, fract_z: float, occupancy: float, adp_type: str, U_iso_or_equiv: float, ADp: list = None, MSp: list = None) -> 'Atom': """ diff --git a/easyInterface/Diffraction/DataClasses/PhaseObj/SpaceGroup.py b/easyInterface/Diffraction/DataClasses/PhaseObj/SpaceGroup.py index 4a6a7bf..24856b4 100755 --- a/easyInterface/Diffraction/DataClasses/PhaseObj/SpaceGroup.py +++ b/easyInterface/Diffraction/DataClasses/PhaseObj/SpaceGroup.py @@ -8,10 +8,10 @@ 'url': 'https://www.iucr.org/__data/iucr/cifdic_html/1/cif_core.dic/Ispace_group_crystal_system.html', 'default': ('', '') }, - 'space_group_name_HM_alt': { + 'space_group_name_HM_ref': { 'header': 'Symbol', - 'tooltip': 'The Hermann-Mauguin symbol of space group.', - 'url': 'https://www.iucr.org/__data/iucr/cifdic_html/1/cif_core.dic/Ispace_group_name_H-M_alt.html', + 'tooltip': 'The short international Hermann-Mauguin space-group symbol.', + 'url': 'https://www.iucr.org/__data/iucr/cifdic_html/2/cif_sym.dic/Ispace_group.name_H-M_ref.html', 'default': ('', '') }, 'space_group_IT_number': { @@ -30,8 +30,8 @@ class SpaceGroup(LoggedPathDict): - def __init__(self, crystal_system: Base, space_group_name_HM_alt: Base, space_group_IT_number: Base, origin_choice: Base): - super().__init__(crystal_system=crystal_system, space_group_name_HM_alt=space_group_name_HM_alt, + def __init__(self, crystal_system: Base, space_group_name_HM_ref: Base, space_group_IT_number: Base, origin_choice: Base): + super().__init__(crystal_system=crystal_system, space_group_name_HM_ref=space_group_name_HM_ref, space_group_IT_number=space_group_IT_number, origin_choice=origin_choice) self._log = logging.getLogger(__class__.__module__) @@ -39,9 +39,9 @@ def __init__(self, crystal_system: Base, space_group_name_HM_alt: Base, space_gr self.setItemByPath(['crystal_system', 'tooltip'], SG_DETAILS['crystal_system']['tooltip']) self.setItemByPath(['crystal_system', 'url'], SG_DETAILS['crystal_system']['url']) - self.setItemByPath(['space_group_name_HM_alt', 'header'], SG_DETAILS['space_group_name_HM_alt']['header']) - self.setItemByPath(['space_group_name_HM_alt', 'tooltip'], SG_DETAILS['space_group_name_HM_alt']['tooltip']) - self.setItemByPath(['space_group_name_HM_alt', 'url'], SG_DETAILS['space_group_name_HM_alt']['url']) + self.setItemByPath(['space_group_name_HM_ref', 'header'], SG_DETAILS['space_group_name_HM_ref']['header']) + self.setItemByPath(['space_group_name_HM_ref', 'tooltip'], SG_DETAILS['space_group_name_HM_ref']['tooltip']) + self.setItemByPath(['space_group_name_HM_ref', 'url'], SG_DETAILS['space_group_name_HM_ref']['url']) self.setItemByPath(['space_group_IT_number', 'header'], SG_DETAILS['space_group_IT_number']['header']) self.setItemByPath(['space_group_IT_number', 'tooltip'], SG_DETAILS['space_group_IT_number']['tooltip']) @@ -53,23 +53,23 @@ def __init__(self, crystal_system: Base, space_group_name_HM_alt: Base, space_gr self._log.debug('Spacegroup created: %s', self) def __repr__(self) -> str: - return 'SpaceGroup: {} {} '.format(self['space_group_name_HM_alt'], self['origin_choice']) + return 'SpaceGroup: {} {} '.format(self['space_group_name_HM_ref'], self['origin_choice']) @classmethod def default(cls) -> 'SpaceGroup': crystal_system = Base(*SG_DETAILS['crystal_system']['default']) - space_group_name_HM_alt = Base(*SG_DETAILS['space_group_name_HM_alt']['default']) + space_group_name_HM_ref = Base(*SG_DETAILS['space_group_name_HM_ref']['default']) space_group_IT_number = Base(*SG_DETAILS['space_group_IT_number']['default']) origin_choice = Base(*SG_DETAILS['origin_choice']['default']) - return cls(crystal_system, space_group_name_HM_alt, space_group_IT_number, origin_choice) + return cls(crystal_system, space_group_name_HM_ref, space_group_IT_number, origin_choice) @classmethod - def fromPars(cls, crystal_system: str, space_group_name_HM_alt: str, space_group_IT_number: int, + def fromPars(cls, crystal_system: str, space_group_name_HM_ref: str, space_group_IT_number: int, origin_choice: str) -> 'SpaceGroup': crystal_system = Base(crystal_system, SG_DETAILS['crystal_system']['default'][1]) - space_group_name_HM_alt = Base(space_group_name_HM_alt, SG_DETAILS['space_group_name_HM_alt']['default'][1]) + space_group_name_HM_ref = Base(space_group_name_HM_ref, SG_DETAILS['space_group_name_HM_ref']['default'][1]) space_group_IT_number = Base(space_group_IT_number, SG_DETAILS['space_group_IT_number']['default'][1]) origin_choice = Base(origin_choice, SG_DETAILS['origin_choice']['default'][1]) - return cls(crystal_system=crystal_system, space_group_name_HM_alt=space_group_name_HM_alt, + return cls(crystal_system=crystal_system, space_group_name_HM_ref=space_group_name_HM_ref, space_group_IT_number=space_group_IT_number, origin_choice=origin_choice) diff --git a/easyInterface/Diffraction/Interface.py b/easyInterface/Diffraction/Interface.py index 0653805..81d521c 100755 --- a/easyInterface/Diffraction/Interface.py +++ b/easyInterface/Diffraction/Interface.py @@ -135,7 +135,7 @@ def setProjectFromCalculator(self) -> NoReturn: try: self.project_dict.setItemByPath(['info', 'modified_datetime'], datetime.fromtimestamp( - os.path.getmtime(self.calculator._main_rcif_path)).strftime( + os.path.getmtime(self.calculator._project_rcif_path)).strftime( '%d %b %Y, %H:%M:%S')) except (TypeError, FileNotFoundError): self.project_dict.setItemByPath(['info', 'modified_datetime'], datetime.min) @@ -163,7 +163,7 @@ def setPhaseDefinition(self, phase_path: str) -> NoReturn: Example:: interface = CalculatorInterface(calculator) - phase_path = '~/Experiments/phases.cif' + phase_path = '~/Experiments/samples.cif' interface.setPhaseDefinition(phase_path) """ self.calculator.setPhaseDefinition(phase_path) @@ -171,6 +171,16 @@ def setPhaseDefinition(self, phase_path: str) -> NoReturn: self.updatePhases() self.updateExperiments() + def addPhaseDefinitionFromString(self, phase_cif_string: str) -> NoReturn: + """ + Set a phase/s to be simulated from a string. + + :param phase_cif_string: String containing the contents of a phase file (`.cif`) + """ + self.calculator.addPhaseDefinitionFromString(phase_cif_string) + # This will re-create all local directories + self.updatePhases() + def addPhaseDefinition(self, phase_path: str) -> NoReturn: """ Add new phases from a cif file to the list of existing crystal phases in the calculator. @@ -253,7 +263,7 @@ def setExperimentDefinition(self, exp_path: str) -> NoReturn: # This will re-create all local directories self.updateExperiments() - def setExperimentDefinitionFromString(self, exp_cif_string: str) -> NoReturn: + def addExperimentDefinitionFromString(self, exp_cif_string: str) -> NoReturn: """ Set an experiment/s to be simulated from a string. Note that this will not have any crystallographic phases associated with it. @@ -309,7 +319,7 @@ def writeMainCif(self, save_dir: str) -> NoReturn: def writePhaseCif(self, save_dir: str) -> NoReturn: """ - Write the `phases.cif` where all phases in the project dictionary are saved to file. This cif file should be + Write the `samples.cif` where all phases in the project dictionary are saved to file. This cif file should be compatible with other crystallographic software. :param save_dir: Directory to where the phases cif file should be saved. @@ -325,16 +335,22 @@ def writeExpCif(self, save_dir: str) -> NoReturn: """ self.calculator.writeExpCif(save_dir) + def writeCalcCif(self, save_dir: str) -> NoReturn: + """ + Write the `calculations.cif` where all calculations in the calculator are saved to file. + + :param save_dir: Directory to where the experiment cif file should be saved. + """ + self.calculator.writeCalcCif(save_dir) + def saveCifs(self, save_dir: str) -> NoReturn: """ - Write project cif files (`main.cif`, `experiments.cif` and `phases.cif`) to a user supplied directory. This - contains all information needed to recreate the project dictionary. + Write project cif files (`main.cif`, `samples.cif`, `experiments.cif` and `calculations.cif`) to a user + supplied directory. This contains all information needed to recreate the project dictionary. :param save_dir: Directory to where the project cif files should be saved. """ - self.writeMainCif(save_dir) - self.writePhaseCif(save_dir) - self.writeExpCif(save_dir) + self.calculator.saveCifs(save_dir) ### # Syncing between Calculator/Dict @@ -352,20 +368,20 @@ def updatePhases(self) -> NoReturn: self.project_dict.setItemByPath(['info', 'phase_ids'], list(phases.keys())) self.project_dict.endBulkUpdate() else: - k, v = self.project_dict['phases'].dictComparison(phases) - if not k: + keys, values, d_updaters = self.project_dict['phases'].dictComparison(phases) + if not keys: return - k = [['phases', *key] for key in k] + keys = [['phases', *key] for key in keys] - k.append(['info', 'phase_ids']) - v.append(list(phases.keys())) + keys.append(['info', 'phase_ids']) + values.append(list(phases.keys())) if self.project_dict.macro_running: - for key, value in zip(k, v): + for key, value in zip(keys, values): self.project_dict.setItemByPath(key, value) else: - self.project_dict.bulkUpdate(k, v, 'Bulk update of phases') + self.project_dict.bulkUpdate(keys, values, 'Bulk update of phases') self.__last_updated = datetime.now() def getPhase(self, phase_name: Union[str, None]) -> Phase: @@ -397,20 +413,20 @@ def updateExperiments(self) -> NoReturn: self.project_dict.setItemByPath(['info', 'experiment_ids'], list(experiments.keys())) self.project_dict.endBulkUpdate() else: - k, v = self.project_dict['experiments'].dictComparison(experiments) + keys, values, d_updaters = self.project_dict['experiments'].dictComparison(experiments) - if not k: + if not keys: return - k = [['experiments', *key] for key in k] + keys = [['experiments', *key] for key in keys] - k.append(['info', 'experiment_ids']) - v.append(list(experiments.keys())) + keys.append(['info', 'experiment_ids']) + values.append(list(experiments.keys())) if self.project_dict.macro_running: - for key, value in zip(k, v): + for key, value in zip(keys, values): self.project_dict.setItemByPath(key, value) else: - self.project_dict.bulkUpdate(k, v, 'Bulk update of experiments') + self.project_dict.bulkUpdate(keys, values, 'Bulk update of experiments') self.__last_updated = datetime.now() def getExperiment(self, experiment_name: Union[str, None]) -> Experiment: @@ -429,6 +445,24 @@ def getExperiment(self, experiment_name: Union[str, None]) -> Experiment: else: raise KeyError + def getExperimentFromCif(self, cif_string: str) -> Experiment: + """ + Create an experiment object from a cif string + :param cif_string: cif formatted string to be interpreted + :returns: Experiment object + """ + new_experiemnt = self.calculator.getExperimentFromCif(cif_string) + return new_experiemnt + + def getPhaseFromCif(self, cif_string: str) -> Phase: + """ + Create an phase object from a cif string + :param cif_string: cif formatted string to be interpreted + :returns: Phase object + """ + new_phase = self.calculator.getPhaseFromCif(cif_string) + return new_phase + @time_it def updateCalculations(self) -> NoReturn: """ @@ -473,9 +507,9 @@ def setPhase(self, phase: Phase) -> NoReturn: if isinstance(phase, Phase): new_phase_name = phase['phasename'] if new_phase_name in self.project_dict['phases'].keys(): - k, v = self.project_dict.getItemByPath(['phases', new_phase_name]).dictComparison(phase) - k = [['phases', new_phase_name, *ik] for ik in k] - self._mappedBulkUpdate(self._mappedValueUpdater, k, v) + keys, values, d_updater = self.project_dict.getItemByPath(['phases', new_phase_name]).dictComparison(phase) + keys = [['phases', new_phase_name, *ik] for ik in keys] + self._mappedBulkUpdate(self._mappedValueUpdater, keys, values) else: self.addPhase(phase) self.__last_updated = datetime.now() @@ -492,14 +526,14 @@ def setPhases(self, phases: Union[Phase, Phases]) -> NoReturn: """ if isinstance(phases, Phase): new_phase_name = phases['phasename'] - k, v = self.project_dict.getItemByPath(['phases', new_phase_name]).dictComparison(phases) - k = [['phases', new_phase_name, *ik] for ik in k] + keys, values, d_updater = self.project_dict.getItemByPath(['phases', new_phase_name]).dictComparison(phases) + keys = [['phases', new_phase_name, *ik] for ik in keys] elif isinstance(phases, Phases): - k = [['phases', item] for item in list(phases.keys())] - v = [phases[key] for key in phases.keys()] + keys = [['phases', item] for item in list(phases.keys())] + values = [phases[key] for key in phases.keys()] else: raise TypeError - self._mappedBulkUpdate(self._mappedValueUpdater, k, v) + self._mappedBulkUpdate(self._mappedValueUpdater, keys, values) self.__last_updated = datetime.now() def setPhaseRefine(self, phase: str, key: List[str], value: bool = True) -> NoReturn: @@ -547,9 +581,9 @@ def setExperiment(self, experiment: Experiment) -> NoReturn: if isinstance(experiment, Experiment): new_phase_name = experiment['name'] if new_phase_name in self.project_dict['experiments'].keys(): - k, v = self.project_dict.getItemByPath(['experiments', new_phase_name]).dictComparison(experiment) - k = [['experiments', new_phase_name, *ik] for ik in k] - self._mappedBulkUpdate(self._mappedValueUpdater, k, v) + keys, values, d_updater = self.project_dict.getItemByPath(['experiments', new_phase_name]).dictComparison(experiment) + keys = [['experiments', new_phase_name, *ik] for ik in keys] + self._mappedBulkUpdate(self._mappedValueUpdater, keys, values) else: self.addExperiment(experiment) else: @@ -694,7 +728,7 @@ def name(self) -> str: """ return self.project_dict["info"]["name"] - def asCifDict(self) -> str: + def asCifDict(self) -> dict: """ Converts the project dictionary into a `cif` structure. @@ -816,6 +850,7 @@ def code_error(keys): self.updateExperiments() self.setCalculatorFromProject() self.__last_updated = datetime.now() + try: update_str = self.project_dict.getItemByPath(key)['mapping'] try: @@ -841,6 +876,7 @@ def code_error(keys): self.updateExperiments() self.setCalculatorFromProject() self.__last_updated = datetime.now() + try: update_str = self.project_dict.getItemByPath(key)['mapping'] try: @@ -849,3 +885,18 @@ def code_error(keys): code_error(key) except (KeyError, TypeError): code_error(key) + + # TODO this section needs to be modified. Main rcif needs to be moved to interface and this implementation removed + def setProjectName(self, value: str) -> NoReturn: + self.calculator.setProjectName(value) + + def getProjectName(self) -> str: + return self.calculator.getProjectName() + + def setProjectKeywords(self, value: list) -> NoReturn: + if isinstance(value, list): + value = '\'%s\'' % ', '.join(value) + self.calculator.setProjectKeywords(value) + + def getProjectKeywords(self) -> list: + return self.calculator.getProjectKeywords() diff --git a/easyInterface/Diffraction/__init__.py b/easyInterface/Diffraction/__init__.py index 6011157..213d603 100644 --- a/easyInterface/Diffraction/__init__.py +++ b/easyInterface/Diffraction/__init__.py @@ -1 +1,8 @@ from easyInterface.Diffraction.Interface import CalculatorInterface + +DEFAULT_FILENAMES = { + 'project': 'project.cif', + 'phases': 'samples.cif', + 'experiments': 'experiments.cif', + 'calculations': 'calculations.cif' +} \ No newline at end of file diff --git a/easyInterface/Utils/DictTools.py b/easyInterface/Utils/DictTools.py index 61e0f84..984a289 100644 --- a/easyInterface/Utils/DictTools.py +++ b/easyInterface/Utils/DictTools.py @@ -199,7 +199,10 @@ def __init__(self, dictionary: 'UndoableDict', key: Union[str, list], value: Any def undo(self) -> NoReturn: if self._new_value is not self._old_value: - self._dictionary._realSetItem(self._key, self._old_value) + if self._old_value is None: + self._dictionary._realDelItem(self._key) + else: + self._dictionary._realSetItem(self._key, self._old_value) def redo(self) -> NoReturn: if self._new_value is not self._old_value: @@ -217,16 +220,12 @@ def __init__(self, dictionary: 'UndoableDict', key: Union[str, list]): self.setText("Removing: {}".format(self._key)) def undo(self) -> NoReturn: - self._dictionary._realSetItemByPath(self._key, self._old_value) + self._dictionary._realAddItemByPath(self._key, self._old_value) def redo(self) -> NoReturn: self._dictionary._realDelItem(self._key) -class RmItem: - pass - - class PathDict(UserDict): """ The PathDict class extends a python dictionary with methods to access its nested @@ -252,7 +251,8 @@ def _realDelItem(self, key: Union[str, list]) -> NoReturn: if isinstance(key, list): del self.getItemByPath(key[:-1])[key[-1]] else: - del self[key] + super().__delitem__(key) + # del self[key] except TypeError as ex: raise KeyError(str(ex)) @@ -260,6 +260,14 @@ def _realSetItemByPath(self, keys: list, value: Any) -> NoReturn: """Actually sets the value in a nested object by the key sequence.""" self.getItemByPath(keys[:-1])[keys[-1]] = value + def _realAddItemByPath(self, keys: list, value: Any) -> NoReturn: + """Actually sets the value in a nested object by the key sequence.""" + item = self.getItemByPath(keys[:-1]) + if isinstance(item, dictdiffer.LIST_TYPES): + item.insert(keys[-1], value) + else: + item[keys[-1]] = value + # Public methods def __setitem__(self, key: str, val: Any) -> NoReturn: @@ -313,7 +321,7 @@ def asDict(self) -> dict: base_dict[key] = item.asDict() return base_dict - def dictComparison(self, another_dict: Union['PathDict', dict], ignore=None) -> Tuple[list, list]: + def dictComparison(self, another_dict: Union['PathDict', dict], ignore=None) -> Tuple[list, list, list]: """ Compare self to a dictionary or PathDict and return the update path and value :param ignore: What to ignore e.g. set(['a'])) @@ -324,6 +332,7 @@ def dictComparison(self, another_dict: Union['PathDict', dict], ignore=None) -> if not isinstance(another_dict, (PathDict, dict)): raise TypeError + type_list = [] key_list = [] value_list = [] @@ -331,38 +340,63 @@ def dictComparison(self, another_dict: Union['PathDict', dict], ignore=None) -> for item in items: type = item[0] - path = item[1] + node = item[1] changes = item[2] - - if isinstance(path, str): - path = path.split(".") - - if type == 'change': - if isinstance(path[-1], int): + if type == dictdiffer.CHANGE: + dest = dictdiffer.utils.dot_lookup(self, node, parent=True) + if isinstance(node, dictdiffer.string_types): + path = node.split('.') + last_node = path[-1] path = path[:-1] - if path in key_list: - continue - new_value = another_dict.getItemByPath(path) - else: - new_value = changes[1] - elif type == 'add': - if not isinstance(changes[0][0], int): - path.append(changes[0][0]) - new_value = changes[0][1] else: - if path in key_list: - continue - new_value = another_dict.getItemByPath(path) - if path[0] == '': - del path[0] - elif type == 'remove': - path = [item[2][0][0]] - new_value = RmItem() - - key_list.append(path) - value_list.append(new_value) - - return key_list, value_list + path = node[:-1] + last_node = node[-1] + if isinstance(dest, dictdiffer.LIST_TYPES): + last_node = int(last_node) + path.append(last_node) + _, new_value = changes + modifier = self.setItemByPath + key_list.append(path) + value_list.append(new_value) + type_list.append(modifier) + elif type == dictdiffer.ADD: + for key, value in changes: + dest = dictdiffer.utils.dot_lookup(self, node) + path = node.split('.') + if isinstance(dest, (dictdiffer.LIST_TYPES, dictdiffer.SET_TYPES)): + if isinstance(dest, dictdiffer.LIST_TYPES): + dest = deepcopy(dest) + dest.insert(key, value) + else: + dest = value + new_value = dest + modifier = self.setItemByPath + else: + path.append(key) + new_value = value + modifier = self.setItemByPath + key_list.append(path) + value_list.append(new_value) + type_list.append(modifier) + + elif type == dictdiffer.REMOVE: + for key, value in changes: + dest = dictdiffer.utils.dot_lookup(self, node) + path = node.split('.') + if isinstance(dest, dictdiffer.SET_TYPES): + new_value = () + modifier = self.setItemByPath + else: + if path[-1] == '': + path = path[:-1] + path.append(key) + new_value = () + modifier = self.rmItemByPath + key_list.append(path) + value_list.append(new_value) + type_list.append(modifier) + + return key_list, value_list, type_list class UndoableDict(PathDict): @@ -382,13 +416,13 @@ def __setitem__(self, key: str, val: Any) -> NoReturn: Calls the undoable command to override PathDict assignment to self[key] implementation and pushes this command on the stack. """ - if isinstance(val, RmItem): - self.__stack.push(_RemoveItemCommand(self, key)) + if key in self: + self.__stack.push(_SetItemCommand(self, key, val)) else: - if key in self: - self.__stack.push(_SetItemCommand(self, key, val)) - else: - self.__stack.push(_AddItemCommand(self, key, val)) + self.__stack.push(_AddItemCommand(self, key, val)) + + def __delitem__(self, key): + self.__stack.push(_RemoveItemCommand(self, key)) @property def macro_running(self) -> bool: @@ -399,8 +433,10 @@ def setItemByPath(self, keys: list, value: Any) -> NoReturn: Calls the undoable command to set a value in a nested object by key sequence and pushes this command on the stack. """ - if isinstance(value, RmItem): - self.__stack.push(_RemoveItemCommand(self, keys)) + # For backwards compatibility + if isinstance(value, tuple): + if len(value) == 0: + self.__stack.push(_RemoveItemCommand(self, keys)) else: self.__stack.push(_SetItemCommand(self, keys, value)) @@ -479,7 +515,9 @@ def bulkUpdate(self, key_list: list, item_list: list, text='Bulk update') -> NoR :param item_list: the value to be updated :return: None """ - self.startBulkUpdate(text) + if text != 'Bulk update': + self.startBulkUpdate(text) for key, value in zip(key_list, item_list): self.setItemByPath(key, value) - self.endBulkUpdate() + if text != 'Bulk update': + self.endBulkUpdate() diff --git a/tests/Data/calculations.cif b/tests/Data/calculations.cif new file mode 100644 index 0000000..e475009 --- /dev/null +++ b/tests/Data/calculations.cif @@ -0,0 +1,2 @@ +data_pd + diff --git a/tests/Data/main.cif b/tests/Data/project.cif similarity index 62% rename from tests/Data/main.cif rename to tests/Data/project.cif index 31522e4..28b63d1 100644 --- a/tests/Data/main.cif +++ b/tests/Data/project.cif @@ -1,4 +1,5 @@ _name Fe3O4 _keywords 'neutron diffraction, powder, 1d' -_phases phases.cif +_samples samples.cif _experiments experiments.cif +_calculations calculations.cif diff --git a/tests/Data/phases.cif b/tests/Data/samples.cif similarity index 100% rename from tests/Data/phases.cif rename to tests/Data/samples.cif diff --git a/tests/easyInterface/Diffraction/Calculators/test_CryspyCalculator.py b/tests/easyInterface/Diffraction/Calculators/test_CryspyCalculator.py index 48e68f6..5ff3155 100644 --- a/tests/easyInterface/Diffraction/Calculators/test_CryspyCalculator.py +++ b/tests/easyInterface/Diffraction/Calculators/test_CryspyCalculator.py @@ -8,10 +8,11 @@ logger.addSysOutput() logger.setLevel(logging.DEBUG) +from easyInterface.Diffraction import DEFAULT_FILENAMES from easyInterface.Diffraction.Calculators import CryspyCalculator test_data = os.path.join("tests", "Data") -file_path = os.path.join(test_data, 'main.cif') +file_path = os.path.join(test_data, DEFAULT_FILENAMES['project']) @pytest.fixture @@ -63,7 +64,7 @@ def test__parse_segment(cal): assert True def test_addExpDefinitionFromString(cal): - file = os.path.join(test_data, 'experiments.cif') + file = os.path.join(test_data, DEFAULT_FILENAMES['experiments']) with open(file, 'r') as file_reader: exp_str = file_reader.read() @@ -72,7 +73,7 @@ def test_addExpDefinitionFromString(cal): def test_set_exps_definition(cal): - file = os.path.join(test_data, 'experiments.cif') + file = os.path.join(test_data, DEFAULT_FILENAMES['experiments']) cal.setExpsDefinition(file) assert len(cal._cryspy_obj.experiments) == 1 assert cal._experiments_path == file @@ -165,7 +166,7 @@ def test_get_phases(cal): assert len(phases) == len(cal._cryspy_obj.crystals) # Spacegroup assert phases.getItemByPath(['Fe3O4', 'spacegroup', 'origin_choice']).value == '2' - assert phases.getItemByPath(['Fe3O4', 'spacegroup', 'space_group_name_HM_alt']).value == 'F 41/d -3 2/m' + assert phases.getItemByPath(['Fe3O4', 'spacegroup', 'space_group_name_HM_ref']).value == 'F d -3 m' # Cell assert phases.getItemByPath(['Fe3O4', 'cell', 'length_a']).value == 8.36212 assert phases.getItemByPath(['Fe3O4', 'cell', 'length_a']).value == phases.getItemByPath( @@ -203,7 +204,7 @@ def test_get_calculations(cal): assert 'pd' in calculations.keys() assert calculations['pd']['name'] == 'pd' assert sum(calculations['pd']['calculated_pattern']['x']) == 16002.0 - assert pytest.approx(calculations['pd']['calculated_pattern']['y_calc'], 370866.19168) + assert pytest.approx(calculations['pd']['calculated_pattern']['y_calc_sum'], 370866.19168) assert calculations['pd']['limits']['main']['x_min'] == 4.0 assert calculations['pd']['limits']['main']['x_max'] == 80.0 assert pytest.approx(calculations['pd']['limits']['main']['y_min'], 102.0307) @@ -251,7 +252,7 @@ def test_set_experiments(cal): cal.setExperiments(experiments) experiments = cal.getExperiments() assert len(experiments) == 1 - assert experiments.getItemByPath(['pd', 'measured_pattern', 'y_obs'])[0] == 500 + assert experiments.getItemByPath(['pd', 'measured_pattern', 'y_obs'])[0] == pytest.approx(767.68, 1E-3) def test_set_obj_from_project_dicts(cal): @@ -309,24 +310,24 @@ def file_tester(path: str, option=None): option = ['main', 'phases', 'experiments'] if 'main' in option: - assert os.path.exists(os.path.join(path, 'main.cif')) - with open(os.path.join(path, 'main.cif'), 'r') as new_reader: + assert os.path.exists(os.path.join(path, DEFAULT_FILENAMES['project'])) + with open(os.path.join(path, DEFAULT_FILENAMES['project']), 'r') as new_reader: new_data = new_reader.read() assert new_data.find('_name Fe3O4') != -1 - assert new_data.find('_phases phases.cif') != -1 - assert new_data.find('_experiments experiments.cif') != -1 + assert new_data.find('_samples %s' % DEFAULT_FILENAMES['phases']) != -1 + assert new_data.find('_experiments %s' % DEFAULT_FILENAMES['experiments']) != -1 elif'phases' in option: - assert os.path.exists(os.path.join(path, 'phases.cif')) - with open(os.path.join(path, 'phases.cif'), 'r') as new_reader: + assert os.path.exists(os.path.join(path, DEFAULT_FILENAMES['phases'])) + with open(os.path.join(path, DEFAULT_FILENAMES['phases']), 'r') as new_reader: new_data = new_reader.read() assert new_data.find('data_Fe3O4') != -1 assert new_data.find('_cell_length_b 8.56212') != -1 assert new_data.find('Fe3B Cani 3.041 3.041 3.041 0.0 0.0 0.0') != -1 - assert new_data.find('Fe3A 2.0 1.0 ') != -1 + assert new_data.find('Fe3A 2.0 1.0') != -1 assert new_data.find('Fe3A Fe3+ 0.125 0.125 0.125 1.0 Uiso 0.0') != -1 elif 'experiments' in option: - assert os.path.exists(os.path.join(path, 'experiments.cif')) - with open(os.path.join(path, 'experiments.cif'), 'r') as new_reader: + assert os.path.exists(os.path.join(path, DEFAULT_FILENAMES['experiments'])) + with open(os.path.join(path, DEFAULT_FILENAMES['experiments']), 'r') as new_reader: new_data = new_reader.read() assert new_data.find('data_pd') != -1 assert new_data.find('_setup_offset_2theta -0.385404') != -1 diff --git a/tests/easyInterface/Diffraction/DataClasses/DataObj/test_Calculation.py b/tests/easyInterface/Diffraction/DataClasses/DataObj/test_Calculation.py index c1b9dbd..c3e5d5b 100644 --- a/tests/easyInterface/Diffraction/DataClasses/DataObj/test_Calculation.py +++ b/tests/easyInterface/Diffraction/DataClasses/DataObj/test_Calculation.py @@ -108,12 +108,12 @@ def test_calculated_pattern(): y_calc = list(range(10, 20)) y_diff_lower = [x_ - 0.1 for x_ in y_calc] y_diff_upper = [x_ + 0.1 for x_ in y_calc] - expected = ['x', 'y_calc', 'y_diff_lower', 'y_diff_upper'] + expected = ['x', 'y_diff_lower', 'y_diff_upper', 'y_calc_up'] expected_type = [list]*4 - cp = CalculatedPattern(x, y_calc, y_diff_lower, y_diff_upper) + cp = CalculatedPattern(x, y_diff_lower, y_diff_upper, y_calc) PathDictTest(cp, expected, expected_type) assert cp['x'] == x - assert cp['y_calc'] == y_calc + assert cp['y_calc_up'] == y_calc assert cp['y_diff_lower'] == y_diff_lower assert cp['y_diff_upper'] == y_diff_upper @@ -156,8 +156,9 @@ def test_calculation_from_pars(): y_diff_lower = [x_ - 0.25 for x_ in x] tth = x y_calc = [x_ * 2 for x_ in x] + y_calc_bkg = [0]*len(y_calc) calc = genericTestCalculation(Calculation.fromPars, name, bragg_crystals, y_obs_lower, - y_obs_upper, tth, y_calc, y_diff_lower, y_diff_upper) + y_obs_upper, tth, y_calc, y_diff_lower, y_diff_upper, y_calc_bkg) def test_calculations(): diff --git a/tests/easyInterface/Diffraction/DataClasses/DataObj/test_Experiment.py b/tests/easyInterface/Diffraction/DataClasses/DataObj/test_Experiment.py index 19d0279..a28f91d 100644 --- a/tests/easyInterface/Diffraction/DataClasses/DataObj/test_Experiment.py +++ b/tests/easyInterface/Diffraction/DataClasses/DataObj/test_Experiment.py @@ -121,8 +121,9 @@ def test_backgrounds(): def test_measured_pattern_default(): - expected = ['x', 'y_obs', 'sy_obs', 'y_obs_up', 'sy_obs_up', 'y_obs_down', 'sy_obs_down'] - expected_type = [*[list]*3, *[(list, type(None))]*4] + expected = ['x', 'y_obs', 'sy_obs', 'y_obs_diff', 'sy_obs_diff', + 'y_obs_up', 'sy_obs_up', 'y_obs_down', 'sy_obs_down'] + expected_type = [*[list]*3, *[(list, type(None))]*6] PathDictDerived(MeasuredPattern.default, expected, expected_type) mp = MeasuredPattern.default() @@ -213,9 +214,10 @@ def test_exp_phase_from_pars(): def genericTestExperiment(exp_constructor, *args): - expected = ['name', 'wavelength', 'offset', 'phase', 'background', 'resolution', 'measured_pattern'] + expected = ['name', 'wavelength', 'offset', 'magnetic_field', 'phase', 'background', 'resolution', 'measured_pattern', 'refinement_type', + 'polarization'] - expected_type = [str, Base, Base, ExperimentPhases, Backgrounds, Resolution, MeasuredPattern] + expected_type = [str, Base, Base, Base, ExperimentPhases, Backgrounds, Resolution, MeasuredPattern, RefinementType, Polarization] PathDictDerived(exp_constructor, expected, expected_type, *args) exp = exp_constructor(*args) diff --git a/tests/easyInterface/Diffraction/DataClasses/PhaseObj/test_SpaceGroup.py b/tests/easyInterface/Diffraction/DataClasses/PhaseObj/test_SpaceGroup.py index 415a1a4..61da941 100644 --- a/tests/easyInterface/Diffraction/DataClasses/PhaseObj/test_SpaceGroup.py +++ b/tests/easyInterface/Diffraction/DataClasses/PhaseObj/test_SpaceGroup.py @@ -3,7 +3,7 @@ def genericTestSG(sg_constructor, *args): - expected = ['crystal_system', 'space_group_name_HM_alt', 'space_group_IT_number', 'origin_choice'] + expected = ['crystal_system', 'space_group_name_HM_ref', 'space_group_IT_number', 'origin_choice'] expected_type = [Base, Base, Base, Base] PathDictDerived(sg_constructor, expected, expected_type, *args) @@ -11,17 +11,17 @@ def genericTestSG(sg_constructor, *args): sg = sg_constructor(*args) assert sg.getItemByPath(['crystal_system', 'header']) == SG_DETAILS['crystal_system']['header'] - assert sg.getItemByPath(['space_group_name_HM_alt', 'header']) == SG_DETAILS['space_group_name_HM_alt']['header'] + assert sg.getItemByPath(['space_group_name_HM_ref', 'header']) == SG_DETAILS['space_group_name_HM_ref']['header'] assert sg.getItemByPath(['space_group_IT_number', 'header']) == SG_DETAILS['space_group_IT_number']['header'] assert sg.getItemByPath(['origin_choice', 'header']) == SG_DETAILS['origin_choice']['header'] assert sg.getItemByPath(['crystal_system', 'tooltip']) == SG_DETAILS['crystal_system']['tooltip'] - assert sg.getItemByPath(['space_group_name_HM_alt', 'tooltip']) == SG_DETAILS['space_group_name_HM_alt']['tooltip'] + assert sg.getItemByPath(['space_group_name_HM_ref', 'tooltip']) == SG_DETAILS['space_group_name_HM_ref']['tooltip'] assert sg.getItemByPath(['space_group_IT_number', 'tooltip']) == SG_DETAILS['space_group_IT_number']['tooltip'] assert sg.getItemByPath(['origin_choice', 'tooltip']) == SG_DETAILS['origin_choice']['tooltip'] assert sg.getItemByPath(['crystal_system', 'url']) == SG_DETAILS['crystal_system']['url'] - assert sg.getItemByPath(['space_group_name_HM_alt', 'url']) == SG_DETAILS['space_group_name_HM_alt']['url'] + assert sg.getItemByPath(['space_group_name_HM_ref', 'url']) == SG_DETAILS['space_group_name_HM_ref']['url'] assert sg.getItemByPath(['space_group_IT_number', 'url']) == SG_DETAILS['space_group_IT_number']['url'] assert sg.getItemByPath(['origin_choice', 'url']) == SG_DETAILS['origin_choice']['url'] @@ -34,8 +34,8 @@ def test_space_group_default(): assert str(sg['crystal_system']['store']['unit']) == SG_DETAILS['crystal_system']['default'][1] assert sg['crystal_system'].value == SG_DETAILS['crystal_system']['default'][0] - assert str(sg['space_group_name_HM_alt']['store']['unit']) == SG_DETAILS['space_group_name_HM_alt']['default'][1] - assert sg['space_group_name_HM_alt'].value == SG_DETAILS['space_group_name_HM_alt']['default'][0] + assert str(sg['space_group_name_HM_ref']['store']['unit']) == SG_DETAILS['space_group_name_HM_ref']['default'][1] + assert sg['space_group_name_HM_ref'].value == SG_DETAILS['space_group_name_HM_ref']['default'][0] assert str(sg['space_group_IT_number']['store']['unit']) == SG_DETAILS['space_group_IT_number']['default'][1] assert sg['space_group_IT_number'].value == SG_DETAILS['space_group_IT_number']['default'][0] @@ -56,8 +56,8 @@ def test_space_group_from_pars(): assert str(sg['crystal_system']['store']['unit']) == SG_DETAILS['crystal_system']['default'][1] assert sg['crystal_system'].value == crystal_system - assert str(sg['space_group_name_HM_alt']['store']['unit']) == SG_DETAILS['space_group_name_HM_alt']['default'][1] - assert sg['space_group_name_HM_alt'].value == space_group_name_HM_alt + assert str(sg['space_group_name_HM_ref']['store']['unit']) == SG_DETAILS['space_group_name_HM_ref']['default'][1] + assert sg['space_group_name_HM_ref'].value == space_group_name_HM_alt assert str(sg['space_group_IT_number']['store']['unit']) == SG_DETAILS['space_group_IT_number']['default'][1] assert sg['space_group_IT_number'].value == space_group_IT_number diff --git a/tests/easyInterface/Diffraction/test_Interface.py b/tests/easyInterface/Diffraction/test_Interface.py index bd158c0..a6e605f 100644 --- a/tests/easyInterface/Diffraction/test_Interface.py +++ b/tests/easyInterface/Diffraction/test_Interface.py @@ -5,9 +5,11 @@ import pytest from copy import deepcopy +from sys import platform # module for testing from tests.easyInterface.Diffraction.DataClasses.Utils.Helpers import PathDictDerived +from easyInterface.Diffraction import DEFAULT_FILENAMES from easyInterface.Diffraction.Calculators import CryspyCalculator from easyInterface.Diffraction.Interface import CalculatorInterface, ProjectDict from easyInterface.Diffraction.DataClasses.Utils.InfoObjs import Interface, App, Calculator, Info @@ -17,9 +19,9 @@ test_data = os.path.join('tests', 'Data') -file_path = os.path.join(test_data, 'main.cif') -phase_path = os.path.join(test_data, 'phases.cif') -exp_path = os.path.join(test_data, 'experiments.cif') +file_path = os.path.join(test_data, DEFAULT_FILENAMES['project']) +phase_path = os.path.join(test_data, DEFAULT_FILENAMES['phases']) +exp_path = os.path.join(test_data, DEFAULT_FILENAMES['experiments']) @pytest.fixture @@ -63,7 +65,7 @@ def test_init(cal): assert len(cal.project_dict['phases']) == 1 assert len(cal.project_dict['experiments']) == 1 - assert cal.calculator._main_rcif_path == file_path + assert cal.calculator._project_rcif_path == file_path assert len(cal.project_dict) == 7 @@ -228,12 +230,12 @@ def test_setExperimentDefinitionFromString(): interface.setPhaseDefinition(phase_path) with open(exp_path, 'r') as file_reader: exp_content = file_reader.read() - interface.setExperimentDefinitionFromString(exp_content) + interface.addExperimentDefinitionFromString(exp_content) experiment_dict = interface.project_dict['experiments'] assert len(experiment_dict) == 1 - assert len(experiment_dict['pd']) == 7 + assert len(experiment_dict['pd']) == 10 # wavelength assert len(experiment_dict['pd']['wavelength']) == 5 assert experiment_dict['pd']['wavelength'].value == 0.84 @@ -272,7 +274,7 @@ def test_setExperimentDefinitionFromString(): assert experiment_dict['pd']['resolution']['v']['store']['hide'] is False # measured_pattern - assert len(experiment_dict['pd']['measured_pattern']) == 7 + assert len(experiment_dict['pd']['measured_pattern']) == 9 assert 5.0 in experiment_dict['pd']['measured_pattern']['x'] assert len(experiment_dict['pd']['measured_pattern'].y_obs_lower) == 381 assert experiment_dict['pd']['measured_pattern'].y_obs_lower[380] == pytest.approx(762.959046) @@ -288,7 +290,7 @@ def test_setExperimentsDictFromCryspyObj(cal): experiment_dict = cal.project_dict['experiments'] assert len(experiment_dict) == 1 - assert len(experiment_dict['pd']) == 7 + assert len(experiment_dict['pd']) == 10 # wavelength assert len(experiment_dict['pd']['wavelength']) == 5 assert experiment_dict['pd']['wavelength'].value == 0.84 @@ -327,7 +329,7 @@ def test_setExperimentsDictFromCryspyObj(cal): assert experiment_dict['pd']['resolution']['v']['store']['hide'] is False # measured_pattern - assert len(experiment_dict['pd']['measured_pattern']) == 7 + assert len(experiment_dict['pd']['measured_pattern']) == 9 assert 5.0 in experiment_dict['pd']['measured_pattern']['x'] assert len(experiment_dict['pd']['measured_pattern'].y_obs_lower) == 381 assert experiment_dict['pd']['measured_pattern'].y_obs_lower[380] == pytest.approx(762.959046) @@ -348,19 +350,19 @@ def test_setCalculationsDictFromCryspyObj(cal): # bragg_peaks assert len(calculation_dict['pd']['bragg_peaks']) == 1 assert sum(calculation_dict['pd']['bragg_peaks']['Fe3O4']['h']) == 681 - assert sum(calculation_dict['pd']['bragg_peaks']['Fe3O4']['ttheta']) == pytest.approx(5027.87268) + assert sum(calculation_dict['pd']['bragg_peaks']['Fe3O4']['ttheta']) == pytest.approx(4993.5717) # calculated_pattern - assert len(calculation_dict['pd']['calculated_pattern']) == 4 + assert len(calculation_dict['pd']['calculated_pattern']) == 8 assert len(calculation_dict['pd']['calculated_pattern']['x']) == 381 assert sum(calculation_dict['pd']['calculated_pattern']['x']) == 16002.0 - assert sum(calculation_dict['pd']['calculated_pattern']['y_diff_upper']) == pytest.approx(37056.915414296) + assert sum(calculation_dict['pd']['calculated_pattern']['y_diff_upper']) == pytest.approx(162647.08) # calculated data limits assert len(calculation_dict['pd']['limits']) == 2 assert calculation_dict['pd']['limits']['main']['x_min'] == 4.0 assert calculation_dict['pd']['limits']['main']['y_max'] == pytest.approx(6134.188081) - assert calculation_dict['pd']['limits']['difference']['y_min'] == pytest.approx(-4087.48283) - assert calculation_dict['pd']['limits']['difference']['y_max'] == pytest.approx(4601.62523) + assert calculation_dict['pd']['limits']['difference']['y_min'] == pytest.approx(-1647.0144) + assert calculation_dict['pd']['limits']['difference']['y_max'] == pytest.approx(5185.9742) def test_phasesCount(cal): @@ -429,11 +431,17 @@ def refineHelper(cal): assert pytest.approx(cal.project_dict['phases']['Fe3O4']['cell']['length_a'].value, 8.36212) r = cal.refine() rr = {'num_refined_parameters': 1, - 'refinement_message': 'Optimization terminated successfully.', - 'nfev': 27, + 'refinement_message': 'Desired error not necessarily achieved due to precision loss.', + 'nfev': 256, 'nit': 5, - 'njev': 9, + 'njev': 83, } + if platform == "darwin": + rr['nfev'] = 274 + rr['njev'] = 89 + elif platform == 'win32': + rr['nfev'] = 271 + rr['njev'] = 88 chi_ref = 3.3723747910939683 chi_found = r['final_chi_sq'] del r['final_chi_sq'] @@ -481,8 +489,8 @@ def test_setPhaseDefinition(cal): phase_ref = cal.getPhase('Fe3O4') assert phase_added['phasename'] == phase_ref['phasename'] assert phase_added['spacegroup']['crystal_system'].value == phase_ref['spacegroup']['crystal_system'].value - assert phase_added['spacegroup']['space_group_name_HM_alt'].value == phase_ref['spacegroup'][ - 'space_group_name_HM_alt'].value + assert phase_added['spacegroup']['space_group_name_HM_ref'].value == phase_ref['spacegroup'][ + 'space_group_name_HM_ref'].value assert phase_added['spacegroup']['space_group_IT_number'].value == phase_ref['spacegroup'][ 'space_group_IT_number'].value assert phase_added['spacegroup']['origin_choice'].value == phase_ref['spacegroup']['origin_choice'].value @@ -505,8 +513,8 @@ def test_addPhaseDefinition(cal): phase_ref = cal.getPhase('Fe3O4') assert phase_added['phasename'] == phase_ref['phasename'] assert phase_added['spacegroup']['crystal_system'].value == phase_ref['spacegroup']['crystal_system'].value - assert phase_added['spacegroup']['space_group_name_HM_alt'].value == phase_ref['spacegroup'][ - 'space_group_name_HM_alt'].value + assert phase_added['spacegroup']['space_group_name_HM_ref'].value == phase_ref['spacegroup'][ + 'space_group_name_HM_ref'].value assert phase_added['spacegroup']['space_group_IT_number'].value == phase_ref['spacegroup'][ 'space_group_IT_number'].value assert phase_added['spacegroup']['origin_choice'].value == phase_ref['spacegroup']['origin_choice'].value @@ -628,24 +636,24 @@ def file_tester(path: str, option=None): option = ['main', 'phases', 'experiments'] if 'main' in option: - assert os.path.exists(os.path.join(path, 'main.cif')) - with open(os.path.join(path, 'main.cif'), 'r') as new_reader: + assert os.path.exists(os.path.join(path, DEFAULT_FILENAMES['project'])) + with open(os.path.join(path, DEFAULT_FILENAMES['project']), 'r') as new_reader: new_data = new_reader.read() assert new_data.find('_name Fe3O4') != -1 - assert new_data.find('_phases phases.cif') != -1 - assert new_data.find('_experiments experiments.cif') != -1 + assert new_data.find('_samples %s' % DEFAULT_FILENAMES['phases']) != -1 + assert new_data.find('_experiments %s' % DEFAULT_FILENAMES['experiments']) != -1 elif'phases' in option: - assert os.path.exists(os.path.join(path, 'phases.cif')) - with open(os.path.join(path, 'phases.cif'), 'r') as new_reader: + assert os.path.exists(os.path.join(path, DEFAULT_FILENAMES['phases'])) + with open(os.path.join(path, DEFAULT_FILENAMES['phases']), 'r') as new_reader: new_data = new_reader.read() assert new_data.find('data_Fe3O4') != -1 assert new_data.find('_cell_length_b 8.36212') != -1 - assert new_data.find('Fe3B Fe3+ 0.5 0.5 0.5 1.0 Uiso d 0.0 16 ') != -1 - assert new_data.find('Fe3A 2.0 1.0 ') != -1 + assert new_data.find('Fe3B Fe3+ 0.5 0.5 0.5 1.0 Uiso 0.0 16 d') != -1 + assert new_data.find('Fe3A 2.0 1.0') != -1 assert new_data.find('Fe3A Cani -3.468 -3.468 -3.468 0.0 0.0 0.0') != -1 elif 'experiments' in option: - assert os.path.exists(os.path.join(path, 'experiments.cif')) - with open(os.path.join(path, 'experiments.cif'), 'r') as new_reader: + assert os.path.exists(os.path.join(path, DEFAULT_FILENAMES['experiments'])) + with open(os.path.join(path, DEFAULT_FILENAMES['experiments']), 'r') as new_reader: new_data = new_reader.read() assert new_data.find('data_pd') != -1 assert new_data.find('_setup_offset_2theta -0.385404') != -1 diff --git a/tests/easyInterface/Utils/test_DictTools.py b/tests/easyInterface/Utils/test_DictTools.py index 5c54299..a6ecedb 100644 --- a/tests/easyInterface/Utils/test_DictTools.py +++ b/tests/easyInterface/Utils/test_DictTools.py @@ -8,7 +8,9 @@ def test_PathDict(): d1 = PathDict(dict(a=1, b=2, c=dict(d=3, e=dict(f=4, g=5)))) d2 = PathDict(dict(a=1, b=2, c=dict(d=333, e=dict(f=4, g=555)))) - assert d1.dictComparison(d2) == ([['c', 'd'], ['c', 'e', 'g']], [333, 555]) + k, v, t = d1.dictComparison(d2) + assert k == [['c', 'd'], ['c', 'e', 'g']] + assert v == [333, 555] d1 = PathDict(dict(a=1, b=2, c=dict(d=3, e=dict(f=4, g=5)))) d2 = "string"