def generate_protocol_files_from_csv(csv_filename, output_directory=None): """ Generates a set of protocol files from csv filename input by reading protocol file input corresponding to each line of the csv file. Writes a csv file that. Args: csv_filename (str): CSV containing protocol file parameters. output_directory (str): directory in which to place the output files """ # Read csv file protocol_params_df = pd.read_csv(csv_filename) new_files = [] names = [] result = "" message = {"comment": "", "error": ""} if output_directory is None: output_directory = PROCEDURE_TEMPLATE_DIR for index, protocol_params in protocol_params_df.iterrows(): template = protocol_params["template"] # Filename for the output filename_prefix = "_".join( [ protocol_params["project_name"], "{:06d}".format(protocol_params["seq_num"]), ] ) # Switch for template invocation if template == "EXP.000": protocol = Procedure.from_exp( **protocol_params[["cutoff_voltage", "charge_rate", "discharge_rate"]] ) filename = "{}.000".format(filename_prefix) filename = os.path.join(output_directory, "procedures", filename) elif template == "diagnosticV2.000": diag_params_df = pd.read_csv( os.path.join(PROCEDURE_TEMPLATE_DIR, "PreDiag_parameters - DP.csv") ) diagnostic_params = diag_params_df[ diag_params_df["diagnostic_parameter_set"] == protocol_params["diagnostic_parameter_set"] ].squeeze() # TODO: should these be separated? protocol = Procedure.from_regcyclev2(protocol_params) protocol.add_procedure_diagcyclev2( protocol_params["capacity_nominal"], diagnostic_params ) filename = "{}.000".format(filename_prefix) filename = os.path.join(output_directory, "procedures", filename) # TODO: how are these different? elif template in ["diagnosticV3.000", "diagnosticV4.000"]: diag_params_df = pd.read_csv( os.path.join(PROCEDURE_TEMPLATE_DIR, "PreDiag_parameters - DP.csv") ) diagnostic_params = diag_params_df[ diag_params_df["diagnostic_parameter_set"] == protocol_params["diagnostic_parameter_set"] ].squeeze() template_fullpath = os.path.join(PROCEDURE_TEMPLATE_DIR, template) protocol = Procedure.generate_procedure_regcyclev3(index, protocol_params, template=template_fullpath) protocol.generate_procedure_diagcyclev3( protocol_params["capacity_nominal"], diagnostic_params ) filename = "{}.000".format(filename_prefix) filename = os.path.join(output_directory, "procedures", filename) elif template == "drivingV1.000": diag_params_df = pd.read_csv( os.path.join(PROCEDURE_TEMPLATE_DIR, "PreDiag_parameters - DP.csv") ) diagnostic_params = diag_params_df[ diag_params_df["diagnostic_parameter_set"] == protocol_params["diagnostic_parameter_set"] ].squeeze() mwf_dir = os.path.join(output_directory, "mwf_files") waveform_name = insert_driving_parametersv1(protocol_params, waveform_directory=mwf_dir) template_fullpath = os.path.join(PROCEDURE_TEMPLATE_DIR, template) protocol = Procedure.generate_procedure_drivingv1(index, protocol_params, waveform_name, template=template_fullpath) protocol.generate_procedure_diagcyclev3( protocol_params["capacity_nominal"], diagnostic_params ) filename = "{}.000".format(filename_prefix) filename = os.path.join(output_directory, "procedures", filename) elif template == "formationV1.mps": protocol = Settings.from_file(os.path.join(BIOLOGIC_TEMPLATE_DIR, template)) protocol = protocol.formation_protocol_bcs(protocol, protocol_params) filename = "{}.mps".format(filename_prefix) filename = os.path.join(output_directory, "settings", filename) else: warnings.warn("Unsupported file template {}, skipping.".format(template)) result = "error" message = { "comment": "Unable to find template: " + template, "error": "Not Found", } continue logger.info(filename, extra=s) if not os.path.isfile(filename): protocol.to_file(filename) new_files.append(filename) names.append(filename_prefix + "_") elif ".sdu" in template: logger.warning("Schedule file generation not yet implemented", extra=s) result = "error" message = { "comment": "Schedule file generation is not yet implemented", "error": "Not Implemented", } # This block of code produces the file containing all of the run file # names produced in this function call. This is to make starting tests easier _, namefile = os.path.split(csv_filename) namefile = namefile.split("_")[0] + "_names_" namefile = namefile + datetime.datetime.now().strftime("%Y%m%d_%H%M") + ".csv" with open( os.path.join(output_directory, "names", namefile), "w", newline="" ) as outputfile: wr = csv.writer(outputfile) for name in names: wr.writerow([name]) outputfile.close() if not result: result = "success" message = { "comment": "Generated {} protocols".format(str(len(new_files))), "error": "", } return new_files, result, message
def test_waveform_from_csv(self): csv_file = os.path.join(TEST_FILE_DIR, "data-share", "raw", "parameters", "Drive_parameters - GP.csv") protocol_params_df = pd.read_csv(csv_file) # Max power in watts in the profiles (should be less than max power limit on cyclers) MAX_PROFILE_CURRENT = 15 MIN_PROFILE_VOLTAGE = 2.7 MAX_PROFILE_POWER = MAX_PROFILE_CURRENT * MIN_PROFILE_VOLTAGE new_files = [] names = [] waveform_names = [] result = "" message = {"comment": "", "error": ""} with ScratchDir(".") as scratch_dir: output_directory = scratch_dir os.makedirs(os.path.join(output_directory, "procedures")) for index, protocol_params in protocol_params_df.iterrows(): template = protocol_params["template"] filename_prefix = "_".join([ protocol_params["project_name"], "{:06d}".format(protocol_params["seq_num"]), ]) if template == "drivingV1.000": diag_params_df = pd.read_csv( os.path.join(PROCEDURE_TEMPLATE_DIR, "PreDiag_parameters - DP.csv")) diagnostic_params = diag_params_df[ diag_params_df["diagnostic_parameter_set"] == protocol_params["diagnostic_parameter_set"]].squeeze() mwf_dir = os.path.join(scratch_dir, "mwf_files") waveform_name = insert_driving_parametersv1( protocol_params, waveform_directory=mwf_dir) template_fullpath = os.path.join(PROCEDURE_TEMPLATE_DIR, template) protocol = Procedure.generate_procedure_drivingv1( index, protocol_params, waveform_name, template=template_fullpath) protocol.generate_procedure_diagcyclev3( protocol_params["capacity_nominal"], diagnostic_params) filename = "{}.000".format(filename_prefix) filename = os.path.join(output_directory, "procedures", filename) waveform_names.append(os.path.split(waveform_name)[-1]) if not os.path.isfile(filename): protocol.to_file(filename) new_files.append(filename) names.append(filename_prefix + "_") waveform_df = pd.read_csv(waveform_name, sep="\t", header=None) power_df = waveform_df[waveform_df[1] == "P"] self.assertLessEqual(power_df[2].abs().max(), MAX_PROFILE_POWER) self.assertGreaterEqual(power_df[2].abs().max(), 0) current_df = waveform_df[waveform_df[1] == "I"] self.assertLessEqual(current_df[2].abs().max(), MAX_PROFILE_CURRENT) self.assertGreaterEqual(current_df[2].abs().max(), 0) discharge_df = waveform_df[waveform_df[0] == "D"] self.assertGreaterEqual(discharge_df[4].abs().max(), MIN_PROFILE_VOLTAGE) self.assertEqual( protocol.get( "MaccorTestProcedure.ProcSteps.TestStep.32.StepType"), 'FastWave') self.assertEqual( protocol.get( "MaccorTestProcedure.ProcSteps.TestStep.64.StepType"), 'FastWave') wave_value = os.path.split(waveform_name)[-1].split(".")[0] self.assertEqual( protocol.get( "MaccorTestProcedure.ProcSteps.TestStep.32.StepValue"), wave_value) self.assertEqual( protocol.get( "MaccorTestProcedure.ProcSteps.TestStep.64.StepValue"), wave_value) self.assertEqual( len(os.listdir(os.path.join(output_directory, "procedures"))), 36) self.assertEqual( len(os.listdir(os.path.join(output_directory, "mwf_files"))), 18) test_names = [ 'US06_x4_24W.MWF', 'LA4_x4_10W.MWF', 'US06_x4_32W.MWF', 'LA4_x4_14W.MWF', 'US06_x4_40W.MWF', 'LA4_x4_18W.MWF', 'US06_x8_24W.MWF', 'LA4_x8_10W.MWF', 'US06_x8_32W.MWF', 'LA4_x8_14W.MWF', 'US06_x8_40W.MWF', 'LA4_x8_18W.MWF', 'US06_x12_24W.MWF', 'LA4_x12_10W.MWF', 'US06_x12_32W.MWF', 'LA4_x12_14W.MWF', 'US06_x12_40W.MWF', 'LA4_x12_18W.MWF', 'US06_x4_24W.MWF', 'LA4_x4_10W.MWF', 'US06_x4_32W.MWF', 'LA4_x4_14W.MWF', 'US06_x4_40W.MWF', 'LA4_x4_18W.MWF', 'US06_x8_24W.MWF', 'LA4_x8_10W.MWF', 'US06_x8_32W.MWF', 'LA4_x8_14W.MWF', 'US06_x8_40W.MWF', 'LA4_x8_18W.MWF', 'US06_x12_24W.MWF', 'LA4_x12_10W.MWF', 'US06_x12_32W.MWF', 'LA4_x12_14W.MWF', 'US06_x12_40W.MWF', 'LA4_x12_18W.MWF' ] self.assertListEqual(test_names, waveform_names)
def generate_protocol_files_from_csv(csv_filename, output_directory=None): """ Generates a set of protocol files from csv filename input by reading protocol file input corresponding to each line of the csv file. Writes a csv file that. Args: csv_filename (str): CSV containing protocol file parameters. output_directory (str): directory in which to place the output files """ # Read csv file protocol_params_df = pd.read_csv(csv_filename) successfully_generated_files = [] file_generation_failures = [] names = [] result = "" message = {"comment": "", "error": ""} if output_directory is None: output_directory = PROCEDURE_TEMPLATE_DIR for index, protocol_params in protocol_params_df.iterrows(): template = protocol_params["template"] protocol = None # Filename for the output filename_prefix = "_".join( [ protocol_params["project_name"], "{:06d}".format(protocol_params["seq_num"]), ] ) if ".000" in template: # Extension for maccor procedure files template_fullpath = os.path.join(PROCEDURE_TEMPLATE_DIR, template) template_length = template_detection(template_fullpath) if "diagnostic_parameter_set" in protocol_params: # For parameters include diagnostics load those values diag_params_df = pd.read_csv( os.path.join(PROCEDURE_TEMPLATE_DIR, "PreDiag_parameters - DP.csv") ) diagnostic_params = diag_params_df[ diag_params_df["diagnostic_parameter_set"] == protocol_params["diagnostic_parameter_set"] ].squeeze() if template_length == 23 and template == "EXP.000": # length and name for initial procedure files protocol = Procedure.from_exp( **protocol_params[["cutoff_voltage", "charge_rate", "discharge_rate"]] ) elif template_length == 72: # length for V1 and V1 diagnostic templates without ending diagnostics protocol = Procedure.from_regcyclev2(protocol_params) protocol.add_procedure_diagcyclev2( protocol_params["capacity_nominal"], diagnostic_params ) elif template_length == 96: # template length for diagnostic type cycling mwf_dir = os.path.join(output_directory, "mwf_files") if protocol_params["project_name"] == "RapidC": # Project with charging waveform waveform_name = insert_charging_parametersv1(protocol_params, waveform_directory=mwf_dir) protocol = Procedure.generate_procedure_chargingv1(index, protocol_params, waveform_name, template=template_fullpath) elif protocol_params["project_name"] == "Drive": # Project with discharging waveform waveform_name = insert_driving_parametersv1(protocol_params, waveform_directory=mwf_dir) protocol = Procedure.generate_procedure_drivingv1(index, protocol_params, waveform_name, template=template_fullpath) else: # Use the default parameterization for PreDiag/Prediction Diagnostic projects protocol = Procedure.generate_procedure_regcyclev3(index, protocol_params, template=template_fullpath) protocol.generate_procedure_diagcyclev3( protocol_params["capacity_nominal"], diagnostic_params ) else: # Case where its not possible to match the procedure template failure = { "comment": "Unable to find template: " + template, "error": "Not Found", } file_generation_failures.append(failure) warnings.warn("Unsupported file template {}, skipping.".format(template)) result = "error" continue filename = "{}.000".format(filename_prefix) filename = os.path.join(output_directory, "procedures", filename) elif ".mps" in template and template == "formationV1.mps": # biologic settings template and formation project protocol = Settings.from_file(os.path.join(BIOLOGIC_TEMPLATE_DIR, template)) protocol = protocol.formation_protocol_bcs(protocol_params) filename = "{}.mps".format(filename_prefix) filename = os.path.join(output_directory, "settings", filename) elif ".sdu" in template: # No schedule file templates implemented failure = { "comment": "Schedule file generation is not yet implemented", "error": "Not Implemented" } file_generation_failures.append(failure) logger.warning("Schedule file generation not yet implemented", extra=s) result = "error" continue else: # Unable to match to any known template format failure = { "comment": "Unable to find template: " + template, "error": "Not Found", } file_generation_failures.append(failure) warnings.warn("Unsupported file template {}, skipping.".format(template)) result = "error" continue logger.info(filename, extra=s) protocol.to_file(filename) successfully_generated_files.append(filename) names.append(filename_prefix + "_") # This block of code produces the file containing all of the run file # names produced in this function call. This is to make starting tests easier _, namefile = os.path.split(csv_filename) namefile = namefile.split("_")[0] + "_names_" namefile = namefile + datetime.datetime.now().strftime("%Y%m%d_%H%M") + ".csv" names_dir = os.path.join(output_directory, "names") os.makedirs(names_dir, exist_ok=True) with open(os.path.join(names_dir, namefile), "w", newline="") as outputfile: wr = csv.writer(outputfile) for name in names: wr.writerow([name]) outputfile.close() num_generated_files = len(successfully_generated_files) num_generation_failures = len(file_generation_failures) num_files = num_generated_files + num_generation_failures message = { "comment": "Generated {} of {} protocols".format(num_generated_files, num_files), "error": "" } if not result: result = "success" else: message["error"] = "Failed to generate {} of {} protocols".format(num_generation_failures, num_files) logger.error(message["error"]) return successfully_generated_files, file_generation_failures, result, message