diff --git a/simetuc/lattice.py b/simetuc/lattice.py index 3c3f697..18f497c 100644 --- a/simetuc/lattice.py +++ b/simetuc/lattice.py @@ -85,6 +85,7 @@ def _check_lattice_settings(cte: Dict): logger.error(msg) raise LatticeError(msg) + def _create_lattice(spacegroup: Union[int, str], cell_par: List[float], num_uc: int, sites_pos: List[float], sites_occ: List[float]) -> ase.Atoms: '''Creates the lattice with the specified parameters. diff --git a/simetuc/settings.py b/simetuc/settings.py index 4105798..9b86342 100644 --- a/simetuc/settings.py +++ b/simetuc/settings.py @@ -97,7 +97,9 @@ def __contains__(self, key: Any) -> bool: def __bool__(self) -> bool: '''Instance is True if all its data structures have been filled out''' for var in vars(self).keys(): - if not var: + print(var) + # If the var is not literally False, but empty + if getattr(self, var) is not False and not getattr(self, var): return False return True @@ -106,10 +108,7 @@ def __eq__(self, other: object) -> bool: if not isinstance(other, Settings): return NotImplemented for attr in ['config_file', 'lattice', 'states', 'excitations', 'decay']: - try: - if self[attr] != other[attr]: - return False - except: + if self[attr] != other[attr]: return False return True @@ -237,7 +236,7 @@ def _parse_states(dict_states: Dict) -> Dict: return parsed_dict @staticmethod - def _parse_excitations(dict_excitations: Dict) -> Dict: + def _parse_excitations(dict_states: Dict, dict_excitations: Dict) -> Dict: '''Parses the excitation section Returns the parsed excitations dict''' logger = logging.getLogger(__name__) @@ -247,6 +246,9 @@ def _parse_excitations(dict_excitations: Dict) -> Dict: 'degeneracy', 'pump_rate'] optional_keys = ['t_pulse'] + sensitizer_labels = dict_states['sensitizer_states_labels'] + activator_labels = dict_states['activator_states_labels'] + # at least one excitation must exist if dict_excitations is None: msg = 'At least one excitation is mandatory' @@ -297,33 +299,12 @@ def _parse_excitations(dict_excitations: Dict) -> Dict: parsed_dict[excitation]['process'] = list_proc # processed in _parse_absorptions parsed_dict[excitation]['active'] = exc_dict['active'] - # at least one excitation must be active - if not any(dict_excitations[label]['active'] for label in dict_excitations.keys()): - msg = 'At least one excitation must be active' - logger.error(msg) - raise ConfigError(msg) - - return parsed_dict - - @staticmethod - def _parse_absorptions(dict_states: Dict, dict_excitations: Dict) -> None: - '''Parse the absorption and add to the excitation label the ion that is excited and - the inital and final states. It makes changes to the argument dictionaries - ''' - logger = logging.getLogger(__name__) - - sensitizer_labels = dict_states['sensitizer_states_labels'] - activator_labels = dict_states['activator_states_labels'] - - # absorption - # for each excitation - for excitation in dict_excitations: - dict_excitations[excitation]['init_state'] = [] - dict_excitations[excitation]['final_state'] = [] - dict_excitations[excitation]['ion_exc'] = [] + parsed_dict[excitation]['init_state'] = [] + parsed_dict[excitation]['final_state'] = [] + parsed_dict[excitation]['ion_exc'] = [] # for each process in the excitation - for process in dict_excitations[excitation]['process']: + for process in list_proc: # get the ion and state labels of the process ion_state_list = _get_ion_and_state_labels(process) @@ -343,29 +324,37 @@ def _parse_absorptions(dict_states: Dict, dict_excitations: Dict) -> None: final_ion_num = _get_state_index(sensitizer_labels, final_state, section='excitation process') - dict_excitations[excitation]['ion_exc'].append('S') + parsed_dict[excitation]['ion_exc'].append('S') elif init_ion == dict_states['activator_ion_label']: # ACTIVATOR init_ion_num = _get_state_index(activator_labels, init_state, section='excitation process') final_ion_num = _get_state_index(activator_labels, final_state, section='excitation process') - dict_excitations[excitation]['ion_exc'].append('A') + parsed_dict[excitation]['ion_exc'].append('A') else: msg = 'Incorrect ion label in excitation: {}'.format(process) logger.error(msg) raise ValueError(msg) # add to list - dict_excitations[excitation]['init_state'].append(init_ion_num) - dict_excitations[excitation]['final_state'].append(final_ion_num) + parsed_dict[excitation]['init_state'].append(init_ion_num) + parsed_dict[excitation]['final_state'].append(final_ion_num) # all ions must be the same in the list! - ion_list = dict_excitations[excitation]['ion_exc'] + ion_list = parsed_dict[excitation]['ion_exc'] if not all(ion == ion_list[0] for ion in ion_list): msg = 'All processes must involve the same ion in {}.'.format(excitation) logger.error(msg) raise ValueError(msg) + # at least one excitation must be active + if not any(dict_excitations[label]['active'] for label in dict_excitations.keys()): + msg = 'At least one excitation must be active' + logger.error(msg) + raise ConfigError(msg) + + return parsed_dict + @staticmethod def _parse_decay_rates(config_cte: Dict) -> Tuple[List[Tuple[int, float]], List[Tuple[int, float]]]: @@ -728,16 +717,14 @@ def load(self, filename: str) -> None: # LATTICE # parse lattice params - self.lattice =self._parse_lattice(config_cte['lattice']) + self.lattice = self._parse_lattice(config_cte['lattice']) # NUMBER OF STATES self.states = self._parse_states(config_cte['states']) # EXCITATIONS - self.excitations = self._parse_excitations(config_cte['excitations']) - - # ABSORPTIONS - self._parse_absorptions(self.states, self.excitations) + self.excitations = self._parse_excitations(config_cte['states'], + config_cte['excitations']) # DECAY RATES pos_value_S, pos_value_A = self._parse_decay_rates(config_cte) diff --git a/simetuc/simulations.py b/simetuc/simulations.py index 229d14f..d504d77 100644 --- a/simetuc/simulations.py +++ b/simetuc/simulations.py @@ -964,8 +964,6 @@ def simulate_steady_state(self, average: bool = False) -> SteadyStateSolution: ''' logger = logging.getLogger(__name__) - cte = self.cte - start_time = time.time() logger.info('Starting simulation...') @@ -985,13 +983,13 @@ def simulate_steady_state(self, average: bool = False) -> SteadyStateSolution: # initial and final times for excitation and relaxation t0 = 0 - tf = (10*np.max(precalculate.get_lifetimes(cte))).round(8) # total simulation time + tf = (10*np.max(precalculate.get_lifetimes(self.cte))).round(8) # total simulation time t0_p = t0 tf_p = tf - N_steps_pulse = cte['simulation_params']['N_steps'] + N_steps_pulse = self.cte['simulation_params']['N_steps'] - rtol = cte['simulation_params']['rtol'] - atol = cte['simulation_params']['atol'] + rtol = self.cte['simulation_params']['rtol'] + atol = self.cte['simulation_params']['atol'] start_time_ODE = time.time() logger.info('Solving equations...') @@ -1019,7 +1017,7 @@ def simulate_steady_state(self, average: bool = False) -> SteadyStateSolution: # store solution and settings steady_sol = SteadyStateSolution(t_pulse, y_pulse, index_S_i, index_A_j, - cte, average=average) + self.cte, average=average) return steady_sol def simulate_avg_steady_state(self) -> SteadyStateSolution: @@ -1076,8 +1074,6 @@ def simulate_concentration_dependence(self, concentration_list: List[Tuple[float logger = logging.getLogger(__name__) logger.info('Simulating power dependence curves...') - cte = self.cte - start_time = time.time() # make sure it's a list of tuple of two floats @@ -1087,11 +1083,11 @@ def simulate_concentration_dependence(self, concentration_list: List[Tuple[float solutions = [] # type: List[Solution] for concs in tqdm(concentration_list, unit='points', - total=num_conc_steps, disable=cte['no_console'], + total=num_conc_steps, disable=self.cte['no_console'], desc='Total progress'): # update concentrations - cte['lattice']['S_conc'] = concs[0] - cte['lattice']['A_conc'] = concs[1] + self.cte['lattice']['S_conc'] = concs[0] + self.cte['lattice']['A_conc'] = concs[1] # simulate if dynamics: sol = self.simulate_dynamics(average=average) # type: Solution @@ -1109,21 +1105,21 @@ def simulate_concentration_dependence(self, concentration_list: List[Tuple[float return conc_dep_solution -#if __name__ == "__main__": -# logger = logging.getLogger() -# logging.basicConfig(level=logging.DEBUG, -# format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s') -# -# logger.info('Called from cmd.') -# -# import simetuc.settings as settings -# cte = settings.load('config_file.cfg') -# -# cte['no_console'] = False -# cte['no_plot'] = False -# -# sim = Simulations(cte) -# +if __name__ == "__main__": + logger = logging.getLogger() + logging.basicConfig(level=logging.DEBUG, + format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s') + + logger.info('Called from cmd.') + + import simetuc.settings as settings + cte = settings.load('config_file.cfg') + + cte['no_console'] = False + cte['no_plot'] = False + + sim = Simulations(cte) + # solution = sim.simulate_dynamics() # solution.log_errors() # solution.plot() @@ -1155,9 +1151,10 @@ def simulate_concentration_dependence(self, concentration_list: List[Tuple[float # # conc_list = [(0, 0.1), (0, 0.2), (0, 0.3)] # conc_list = [(0, 0.1), (0, 0.2), (0, 0.3), (0.1, 0.1), (0.1, 0.2), (0.1, 0.3)] -# solution = sim.simulate_concentration_dependence(conc_list, dynamics=False) -# solution.plot() -# solution.save() + conc_list = [(0, 0.3), (0.1, 0.3), (0.1, 0)] + solution = sim.simulate_concentration_dependence(conc_list, dynamics=False) + solution.plot() + solution.save() # # new_sol = ConcentrationDependenceSolution() # new_sol.load('results/bNaYF4/data_30uc_0.0S_0.3A_conc_dep.hdf5') diff --git a/simetuc/test/test_settings/test_settings.py b/simetuc/test/test_settings/test_settings.py index 983976f..324594f 100644 --- a/simetuc/test/test_settings/test_settings.py +++ b/simetuc/test/test_settings/test_settings.py @@ -15,12 +15,8 @@ test_folder_path = os.path.dirname(os.path.abspath(__file__)) -def test_standard_config(): - filename = os.path.join(test_folder_path, 'test_standard_config.txt') - cte = settings.load(filename) - - with open(filename, 'rt') as file: - config_file = file.read() +@pytest.fixture(scope='function') +def setup_cte(): cte_good = dict([ ('lattice', @@ -144,18 +140,46 @@ def test_standard_config(): 'mult': 6, 'type': 'SS', 'value': 45022061400.0})]))]) - cte_good['config_file'] = config_file - assert cte == settings.Settings(cte_good) -def test_non_existing_config(): + return cte_good + +def test_standard_config(setup_cte): + ''''Test that the returned Settings instance for a know config file is correct.''' + filename = os.path.join(test_folder_path, 'test_standard_config.txt') + cte = settings.load(filename) + + with open(filename, 'rt') as file: + config_file = file.read() + + setup_cte['config_file'] = config_file + + assert cte == settings.Settings(setup_cte) + + +def test_settings_class(setup_cte): + '''Test the creation, bool, and equality of Settings instances.''' + + empty_settings = settings.Settings() + assert not empty_settings + + settings1 = settings.Settings(setup_cte) + settings2 = settings.Settings(setup_cte) + assert settings1 == settings2 + + setup_cte['lattice']['name'] = 'new_name' + settings3 = settings.Settings(setup_cte) + assert settings3 != settings1 + + +def test_non_existing_file(): with pytest.raises(settings.ConfigError) as excinfo: # load non existing file settings.load(os.path.join(test_folder_path, 'test_non_existing_config.txt')) assert excinfo.match(r"Error reading file") assert excinfo.type == settings.ConfigError -def test_empty_config(): +def test_empty_file(): with pytest.raises(settings.ConfigError) as excinfo: with temp_config_filename('') as filename: settings.load(filename) @@ -612,7 +636,12 @@ def test_excitations_config8(): pump_rate: 9.3e-4 # cm2/J ''' exc_dict = yaml.load(data_exc) - settings.Settings._parse_excitations(exc_dict) + + states_dict = {'activator_states_labels': ['3H6', '3F4', '3H5', '3H4', '3F3', '1G4', '1D2'], + 'sensitizer_states_labels': ['GS', 'ES'], + 'activator_ion_label': 'Tm', + 'sensitizer_ion_label': 'Yb'} + settings.Settings._parse_excitations(states_dict, exc_dict) def test_abs_config1(): data = data_states_ok + '''excitations: diff --git a/simetuc/test/test_simulations/test_simulations.py b/simetuc/test/test_simulations/test_simulations.py index a42afa2..a9871ff 100644 --- a/simetuc/test/test_simulations/test_simulations.py +++ b/simetuc/test/test_simulations/test_simulations.py @@ -141,7 +141,6 @@ def test_sim(setup_cte): '''Test that the simulations work''' setup_cte['lattice']['S_conc'] = 0 sim = simulations.Simulations(setup_cte) - print(sim.cte) assert sim.cte assert sim @@ -389,10 +388,9 @@ def test_sim_power_dep4(setup_cte, mocker): assert mocked.call_count == len(power_dens_list) for num, pow_dens in enumerate(power_dens_list): - print(num, pow_dens) assert solution[num].power_dens == pow_dens -def test_sim_conc_dep1(setup_cte, mocker): +def test_sim_conc_dep_steady(setup_cte, mocker): '''Test that the concentration dependence works''' with temp_bin_filename() as temp_filename: mocked = mocker.patch('simetuc.odesolver._solve_ode') @@ -417,7 +415,7 @@ def test_sim_conc_dep1(setup_cte, mocker): assert sol_hdf5 == solution sol_hdf5.plot() -def test_sim_conc_dep2(setup_cte, mocker): +def test_sim_conc_dep_dyn(setup_cte, mocker): '''Test that the concentration dependence works''' with temp_bin_filename() as temp_filename: mocked = mocker.patch('simetuc.odesolver._solve_ode') @@ -443,7 +441,24 @@ def test_sim_conc_dep2(setup_cte, mocker): assert sol_hdf5 == solution sol_hdf5.plot() -def test_sim_conc_dep3(setup_cte, mocker): +def test_sim_conc_dep_list(setup_cte, mocker): + '''Test that the concentration dependence works''' + with temp_bin_filename() as temp_filename: + mocked = mocker.patch('simetuc.odesolver._solve_ode') + # the num_states changes when the temp lattice is created, + # allocate 2x so that we're safe. Also make the num_points 1000. + mocked.return_value = np.random.random((1000, 2*setup_cte['states']['energy_states'])) + + conc_list = [(0, 0.3), (0.1, 0.3), (0.1, 0)] + sim = simulations.Simulations(setup_cte, full_path=temp_filename) + solution = sim.simulate_concentration_dependence(conc_list, dynamics=True) + # dynamics call solve_ode twice (pulse and relaxation) + assert mocked.call_count == 2*len(conc_list) + + for num, conc in enumerate(conc_list): + assert solution[num].concentration == conc + +def test_sim_conc_dep_only_A(setup_cte, mocker): '''Conc list has only A changing''' with temp_bin_filename() as temp_filename: mocked = mocker.patch('simetuc.odesolver._solve_ode') @@ -459,7 +474,7 @@ def test_sim_conc_dep3(setup_cte, mocker): assert solution solution.plot() -def test_sim_conc_dep4(setup_cte, mocker): +def test_sim_conc_dep_only_S(setup_cte, mocker): '''Conc list has only S changing''' with temp_bin_filename() as temp_filename: mocked = mocker.patch('simetuc.odesolver._solve_ode') @@ -475,7 +490,7 @@ def test_sim_conc_dep4(setup_cte, mocker): assert solution solution.plot() -def test_sim_conc_dep5(setup_cte, recwarn): +def test_sim_conc_dep_empty_conc(setup_cte, recwarn): '''Conc list is empty''' with temp_bin_filename() as temp_filename: sim = simulations.Simulations(setup_cte, full_path=temp_filename) @@ -490,7 +505,7 @@ def test_sim_conc_dep5(setup_cte, recwarn): assert 'Nothing to plot! The concentration_dependence list is emtpy!' in str(warning.message) -def test_sim_conc_dep6(setup_cte, recwarn, mocker): +def test_sim_conc_dep_no_plot(setup_cte, recwarn, mocker): '''A plot was requested, but no_plot is set''' setup_cte['no_plot'] = True with temp_bin_filename() as temp_filename: