Source code for pySIMsalabim.experiments.JV_steady_state

"""Perform steady-state JV simulations"""

######### Package Imports #########################################################################

import os, uuid, sys
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy import constants
# import pySIMsalabim
## Import pySIMsalabim, if not successful, add the parent directory to the system path
try :
    import pySIMsalabim as sim
except ImportError:
    # Add the parent directory to the system path
    sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')))
    import pySIMsalabim as sim
from pySIMsalabim.utils import general as utils_gen
from pySIMsalabim.utils.parallel_sim import *
from pySIMsalabim.utils.utils import update_cmd_pars

######### Functions #################################################################################

[docs] def run_SS_JV(simss_device_parameters, session_path, JV_file_name = 'JV.dat', varFile = 'none', G_fracs = [], parallel = False, max_jobs = max(1,os.cpu_count()-1), run_mode = True, **kwargs): """ Parameters ---------- simss_device_parameters : string Name of the device parameters file. session_path : string Path to the session folder where the simulation will run. JV_file_name : string Name of the JV file. parallel : bool, optional Run the simulations in parallel, by default False max_jobs : int, optional Maximum number of parallel jobs, by default max(1,os.cpu_count()-1) cmd_pars : _type_, optional _description_, by default None UUID : str, optional _description_, by default '' run_mode : bool, optional indicate whether the script is in 'web' mode (True) or standalone mode (False). Used to control the console output, by default True **kwargs : dict Additional arguments to be passed to the function. Returns ------- int Exitcode of the simulation. str Message from the simulation. """ verbose = kwargs.get('verbose', False) # Check if the user wants to show verbose output in the console UUID = kwargs.get('UUID', '') # Check if the user wants to add a UUID to the JV file name cmd_pars = kwargs.get('cmd_pars', None) # Check if the user wants to add additional command line parameters to the force_multithreading = kwargs.get('force_multithreading', False) # Check if the user wants to force multithreading instead of using GNU parallel cmd_pars = kwargs.get('cmd_pars', None) # Check if the user wants to add additional command line parameters # Check if the user wants to force the use of thread safe mode, necessary for Windows with parallel simulations if os.name == 'nt': threadsafe = kwargs.get('threadsafe', True) # Check if the user wants to force the use of threads instead of processes else: threadsafe = kwargs.get('threadsafe', False) # Check if the user wants to force the use of threads instead of processes turnoff_autoTidy = kwargs.get('turnoff_autoTidy', None) # Check if the user wants to turn off the autoTidy function in SIMsalabim if turnoff_autoTidy is None: if not threadsafe: turnoff_autoTidy = True else: turnoff_autoTidy = False # Update the JV file name with the UUID if UUID != '': dum_str = f'_{UUID}' else: dum_str = '' # Define the command to be executed if G_fracs is None: # Update the filenames with the UUID JV_file_name = os.path.join(session_path,JV_file_name) logFile = os.path.join(session_path,'log.txt') scParsFile = os.path.join(session_path,'scPars.txt') if UUID != '': JV_file_name_base, JV_file_name_ext = os.path.splitext(JV_file_name) JV_file_name = JV_file_name_base + dum_str + JV_file_name_ext logFile = os.path.join(session_path,'log'+dum_str+'.txt') scParsFile = os.path.join(session_path,'scPars'+dum_str+'.txt') if varFile != 'none': var_file_base, var_file_ext = os.path.splitext(varFile) varFile = var_file_base + dum_str + var_file_ext varFile = os.path.join(session_path,varFile) # Specify the arguments to be attached to the cmd SS_JV_args = [{'par':'dev_par_file','val':simss_device_parameters}, {'par':'JVFile','val':JV_file_name}, {'par':'logFile','val':logFile}, {'par':'scParsFile','val':scParsFile} ] if turnoff_autoTidy: SS_JV_args.append({'par':'autoTidy','val':'0'}) if varFile != 'none': SS_JV_args.append({'par':'varFile','val':varFile}) # Update the cmd_pars with the SS_JV_args if cmd_pars is not None: SS_JV_args = update_cmd_pars(SS_JV_args, cmd_pars) if threadsafe: # Run the simulation in thread safe mode result, message = utils_gen.run_simulation_filesafe('simss', SS_JV_args, session_path, run_mode = run_mode,verbose=verbose) else: result, message = utils_gen.run_simulation('simss',SS_JV_args,session_path,run_mode = run_mode,verbose=verbose) return result, message else: # Update the filenames with the UUID JV_file_name = os.path.join(session_path,JV_file_name) JV_file_name_base, JV_file_name_ext = os.path.splitext(JV_file_name) if varFile != 'none': var_file_base, var_file_ext = os.path.splitext(varFile) varFile = var_file_base + dum_str + var_file_ext varFile = os.path.join(session_path,varFile) # SS_JV_args = [{'par':'dev_par_file','val':simss_device_parameters}] SS_JV_args_list = [] for G_frac in G_fracs: dum_args = [{'par':'dev_par_file','val':simss_device_parameters}, {'par':'G_frac','val':str(G_frac)}, {'par':'JVFile','val':JV_file_name_base + f'_Gfrac_{G_frac}' + dum_str + JV_file_name_ext}, {'par':'logFile','val':os.path.join(session_path,'log'+f'_Gfrac_{G_frac}'+dum_str+'.txt')}, {'par':'scParsFile','val':os.path.join(session_path,'scPars'+f'_Gfrac_{G_frac}'+dum_str+'.txt')}, {'par':'varFile','val':os.path.join(session_path,var_file_base + f'_Gfrac_{G_frac}' + dum_str +var_file_ext)} if varFile != 'none' else {'par':'varFile','val':'none'} ] if turnoff_autoTidy: dum_args.append({'par':'autoTidy','val':'0'}) if cmd_pars is not None: dum_args = update_cmd_pars(dum_args, cmd_pars) SS_JV_args_list.append(dum_args) if parallel and len(G_fracs) > 1: results = run_simulation_parallel('simss', SS_JV_args_list, session_path, max_jobs, force_multithreading=force_multithreading,verbose=verbose) msg_list = ['' for i in range(len(results))] else: results, msg_list = [], [] for dum_args in SS_JV_args_list: if threadsafe: result, message = utils_gen.run_simulation_filesafe('simss', dum_args, session_path, run_mode,verbose=verbose) else: result, message = utils_gen.run_simulation('simss', dum_args, session_path, run_mode,verbose=verbose) results.append(result) msg_list.append(message) # check if results is a list of CompletedProcess objects if isinstance(results, list) : if len(results) > 0 and not isinstance(results[0], subprocess.CompletedProcess): pass else: if len(results) > 0 and isinstance(results[0], tuple) and all(isinstance(res[0], subprocess.CompletedProcess) for res in results): # Extract the return codes from the CompletedProcess objects results = [res[0].returncode for res in results] # Check if all simulations were successful if all([res == 0 for res in results]): if verbose and not run_mode: print('All JV simulations completed successfully\n') for mess in msg_list: print(mess) return 0, 'All JV simulations completed successfully' elif all([(res == 0 or res == 95) for res in results]): if verbose and not run_mode: print('All JV simulations completed successfully, but some had some points that did not converge\n') for mess in msg_list: print(mess) return 0, 'All JV simulations completed successfully, but some had some points that did not converge' else: if verbose and not run_mode: print('Some JV simulations failed\n') for i, res in enumerate(results): print(f'Simulation {i+1} failed with return code {res}') # get all results that are not 0 failed_results = [res for i, res in enumerate(results) if res != 0 and res != 95] # If there is only one failed result, return it with the error message if len(failed_results) == 1: return failed_results[0], utils_gen.error_message(failed_results[0]) else: return 666, utils_gen.error_message(666)
## Running the function as a standalone script if __name__ == "__main__": ## Manual Steady State input parameters. These are overwritten by command line arguments if provided # G_fracs = [0.9, 1.0] G_fracs = None # Define folder and file paths session_path = os.path.join('../../','SIMsalabim','SimSS') simss_device_parameters = 'simulation_setup.txt' JV_name = 'JV.dat' Var_name = 'Var.dat' # UUID = str(uuid.uuid4()) # Add a UUID to the simulation UUID = '' # Not user input parallel = False run_mode = False # If False, show verbose output in console ############## Command line arguments ############## ## Notes ## - The command line arguments are optional and can be provided in any order ## - Each command line argument must be provided in the format -par_name value ## - Possible arguments include all SIMsalabim parameters and Steady State JV specific parameters as listed before ## - Special arguments ## - G_fracs : string ## - The G fractions to simulate, separated by a comma ## - sp : string ## - The session path, i.e. the working directory for the simulation ## - simsetup : string ## - The name of the simss simulation setup parameters file ## - UUID : string ## - An UUID to add to the simulation (output) cmd_pars_dict = {} cmd_pars = [] # Check if any arguments are provided. if len(sys.argv) >= 2: # Each arguments should be in the format -par val, i.e. in pairs of two. Skip the first argument as this is the script name if not len(sys.argv[1:]) % 2 == 0: print('Error in command line parameters. Please provide arguments in the format -par_name value -par_name value ...') sys.exit(1) else: input_list = sys.argv[1:] # Loop over the input list and put pairs into a dictionary with the first argument as key and the second argument as value for i in range(0,len(input_list),2): # Check if the key already exists, if not, add it to the cmd_pars_dict if str(input_list[i][1:]) in cmd_pars_dict: print(f'Duplicate parameter found in the command line parameters: {str(input_list[i][1:])}') sys.exit(1) else: cmd_pars_dict[str(input_list[i][1:])] = str(input_list[i+1]) # Check and process specific keys/arguments that are not native SIMsalabim arguments # Handle the session_path/sp argument separately, as the other parameters depend on this if 'sp' in cmd_pars_dict: session_path = cmd_pars_dict['sp'] # remove from cmd_pars cmd_pars_dict.pop('sp') # Define mappings for keys and variables key_action_map = { 'simsetup': lambda val: {'zimt_device_parameters': val}, 'G_fracs': lambda val: {'G_fracs': val.split(',')}, # IMPORTANT: if multiple Gfracs are provided, they must be separated by a comma!! 'JV_name': lambda val: {'JV_name': val}, 'Var_name': lambda val: {'Var_name': val}, 'UUID': lambda val: {'UUID': val}, } for key in list(cmd_pars_dict.keys()): # Use list to avoid modifying the dictionary while iterating if key in key_action_map: # Apply the corresponding action result = key_action_map[key](cmd_pars_dict[key]) globals().update(result) # Dynamically update global variables cmd_pars_dict.pop(key) # Handle remaining keys in `cmd_pars_dict` and add them to the cmd_pars list cmd_pars.extend({'par': key, 'val': value} for key, value in cmd_pars_dict.items()) # Run the function results, message = run_SS_JV(simss_device_parameters, session_path, JV_name, Var_name, G_fracs, parallel = parallel, run_mode = run_mode, cmd_pars=cmd_pars, UUID=UUID) # Print the results if simulation failed if run_mode: if G_fracs == None: print(message) else: if results != 0: for i, msg in enumerate(message): print(f'G_frac: {G_fracs[i]} - {msg}')