diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a4bb9c9..e71422d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -49,9 +49,10 @@ repos: hooks: - id: flake8 # E501 - line length limit + # E502 - backslash in brackets # E741 - ambiguous variable name, used sparsely when appropriate # F541 - f-string is missing placeholders - args: ["--ignore=E501,E741,F541"] + args: ["--ignore=E501,E502,E741,F541"] exclude: *exclude_files - repo: https://github.com/pre-commit/mirrors-mypy diff --git a/aiida_aurora/utils/parsers.py b/aiida_aurora/utils/parsers.py index 8b25acd..7438d33 100644 --- a/aiida_aurora/utils/parsers.py +++ b/aiida_aurora/utils/parsers.py @@ -4,14 +4,32 @@ from aiida.orm import ArrayData -def get_data_from_raw(jsdata) -> dict: - "Extract raw data from json file." +def get_data_from_raw(jsdata: dict) -> dict: + """Extract raw data from json file. + + Parameters + ---------- + `jsdata` : `dict` + The raw JSON data. + + Returns + ------- + `dict` + The post-processed data. + + Raises + ------ + `TypeError` + If `jsdata` is not a dictionary. + `NotImplementedError` + If `jsdata` contains more than one step. + """ if not isinstance(jsdata, dict): - raise TypeError('jsdata should be a dictionary') + raise TypeError("jsdata should be a dictionary") if len(jsdata["steps"]) > 1: - raise NotImplementedError('Analysis of multiple steps is not implemented.') + raise NotImplementedError("multi-step analysis not implemented.") raw_data = jsdata["steps"][0]["data"] @@ -23,50 +41,99 @@ def get_data_from_raw(jsdata) -> dict: return post_process_data(t, Ewe, I) -def get_data_from_results(array_node) -> dict: - "Extract data from parsed ArrayData node." +def get_data_from_results(array_node: ArrayData) -> dict: + """Extract data from parsed ArrayData node. + + Parameters + ---------- + `array_node` : `ArrayData` + The cycling experiment results node. + + Returns + ------- + `dict` + The post-processed data. + + Raises + ------ + `TypeError` + If `array_node` is not an `ArrayData` node. + """ if not isinstance(array_node, ArrayData): - raise TypeError('array_node should be an ArrayData') + raise TypeError("array_node should be an ArrayData") # collect data - t = array_node.get_array('step0_uts') + t = array_node.get_array("step0_uts") t -= t[0] - Ewe = array_node.get_array('step0_Ewe_n') - I = array_node.get_array('step0_I_n') + Ewe = array_node.get_array("step0_Ewe_n") + I = array_node.get_array("step0_I_n") return post_process_data(t, Ewe, I) def post_process_data(t: np.ndarray, Ewe: np.ndarray, I: np.ndarray) -> dict: - """docstring""" + """Post-process raw data. + + Processes: + - Determine half-cycle markers + - Integrate continuous charge + - Collect charge/discharge capacities + - + + Parameters + ---------- + `t` : `np.ndarray` + The raw time data [s]. + `Ewe` : `np.ndarray` + The raw voltage data [V]. + `I` : `np.ndarray` + The raw current data [A]. + + Returns + ------- + `dict` + The post-processed data. + """ mask = I != 0 # filter out zero current t, Ewe, I = t[mask], Ewe[mask], I[mask] + Q = cumtrapz(I, t, axis=0, initial=0) + # mark half-cycles (including first and last values) idx = np.where(np.diff(np.sign(I), prepend=0) != 0)[0] idx = np.append(idx, len(I)) - # integrate and store charge and discharge currents - Qc, Qd = [], [] + # integrate and store charge/discharge capacities/energies + cycle_idx, Qc, Qd, Ec, Ed = [], [], [], [], [] + for ii in range(len(idx) - 1): + i0, ie = idx[ii], idx[ii + 1] + if ie - i0 < 10: continue - q = np.trapz(I[i0:ie], t[i0:ie]) - if q > 0: + + e = np.trapz(Ewe[i0:ie], Q[i0:ie]) + + if (q := np.trapz(I[i0:ie], t[i0:ie])) > 0: + cycle_idx.append(i0) Qc.append(q) + Ec.append(e) else: Qd.append(abs(q)) + Ed.append(abs(e)) return { - 'time': t, - 'Ewe': Ewe, - 'I': I, - 'cn': len(Qd), - 'time-cycles': t[idx[2::2]], - 'Q': cumtrapz(I, t, axis=0, initial=0) / 3.6, - 'Qd': np.array(Qd) / 3.6, - 'Qc': np.array(Qc) / 3.6, + "time": t, # [s] + "Ewe": Ewe, # [V] + "I": I, # [A] + "Q": Q / 3.6, # [mAh] + "cycle-number": np.arange(len(Qd)), + "cycle-index": np.array(cycle_idx), + "Qc": np.array(Qc) / 3.6, # [mAh] + "Qd": np.array(Qd) / 3.6, # [mAh] + "Ec": np.array(Ec) / 3600, # [Wh] + "Ed": np.array(Ed) / 3600, # [Wh] }