From fa2e2d3bbdfc2512272bfa3b40db43be020f2cc2 Mon Sep 17 00:00:00 2001 From: Sara Shi Date: Fri, 12 Apr 2024 02:15:38 -0400 Subject: [PATCH 1/3] bode bode --- src/autoeis/visualization.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/autoeis/visualization.py b/src/autoeis/visualization.py index b00eae58..0aea0dd2 100644 --- a/src/autoeis/visualization.py +++ b/src/autoeis/visualization.py @@ -210,22 +210,24 @@ def plot_impedance_combo( if label is not None: ax[0].legend() - # Bode plot (magnitude) <- Re(Z) + # Bode plot (magnitude) |Z| if not isinstance(ax[1], list): - ax[1] = [ax[1], ax[1].twinx()] - plot = getattr(ax[1][0], "scatter" if scatter else "plot") - plot(freq, Re_Z, color="blue", label=r"$Re(Z)$", **kwargs) + ax[1] = [ax[1], ax[1].twinx()] # Create a twin axis if not already present + plot_magnitude = getattr(ax[1][0], "scatter" if scatter else "plot") + magnitude_Z = np.sqrt(Re_Z**2 + Im_Z**2) # Calculate magnitude of Z + plot_magnitude(freq, magnitude_Z, color="blue", label=r"$|Z|$", **kwargs) ax[1][0].set_xscale("log") - ax[1][0].set_xlabel("freq (Hz)") - ax[1][0].set_ylabel(r"$Re(Z) / \Omega$") + ax[1][0].set_xlabel("Frequency (Hz)") + ax[1][0].set_ylabel(r"$|Z| / \Omega$") ax[1][0].yaxis.label.set_color("blue") - # Bode plot (phase) <- Im(Z) - plot = getattr(ax[1][1], "scatter" if scatter else "plot") - plot(freq, -Im_Z, color="red", label=r"$-Im(Z)$", **kwargs) - ax[1][1].set_ylabel(r"$-Im(Z) / \Omega$") + # Bode plot (phase) Phase(Z) + plot_phase = getattr(ax[1][1], "scatter" if scatter else "plot") + phase_Z = np.degrees(np.arctan2(Im_Z, Re_Z)) # Calculate phase of Z in degrees + plot_phase(freq, phase_Z, color="red", label=r"$\text{Phase}(Z)$", **kwargs) + ax[1][1].set_ylabel(r"$\text{Phase}(Z) \, (\degree)$") ax[1][1].yaxis.label.set_color("red") - # Don't show grid lines for the second y-axis (ax[1][0] already has them) + # No grid lines for the second y-axis as the primary y-axis already has them ax[1][1].grid(False) fig.tight_layout() From f08175c90b43cb3c1db1b46761588add6d85251f Mon Sep 17 00:00:00 2001 From: Sara Shi Date: Fri, 12 Apr 2024 15:15:43 -0400 Subject: [PATCH 2/3] bode plot --- src/autoeis/visualization.py | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/src/autoeis/visualization.py b/src/autoeis/visualization.py index 0aea0dd2..171f9cf0 100644 --- a/src/autoeis/visualization.py +++ b/src/autoeis/visualization.py @@ -161,12 +161,12 @@ def plot_nyquist( def plot_impedance_combo( - freq: np.ndarray[float], - Z: np.ndarray[complex], + freq: np.ndarray, + Z: np.ndarray, size: int = 10, ax: list[plt.Axes] = None, - scatter=True, - label=None, + scatter: bool = True, + label: str = None ) -> tuple[plt.Figure, list[plt.Axes]]: """Plots EIS data in Nyquist and Bode plots. @@ -190,23 +190,19 @@ def plot_impedance_combo( tuple[plt.Figure, list[plt.Axes]] Figure and axes of the plots. """ - Re_Z = Z.real - Im_Z = Z.imag - if ax is None: - fig, ax = plt.subplots(ncols=2) - assert not isinstance(ax, Axes), "Incompatible 'ax'. Use plt.subplots(ncols=2)" - fig = ax[0].figure - fig.set_size_inches(8, 3) + fig, ax = plt.subplots(ncols=2, figsize=(8, 3)) + else: + assert len(ax) == 2 and all(isinstance(a, Axes) for a in ax), "Incompatible 'ax'. Use plt.subplots(ncols=2)" + fig = ax[0].figure # Nyquist plot plot = getattr(ax[0], "scatter" if scatter else "plot") kwargs = {"s": size} if scatter else {} - plot(Re_Z, -Im_Z, label=label, **kwargs) + plot(Z.real, -Z.imag, label=label, **kwargs) ax[0].set_xlabel(r"$Re(Z) / \Omega$") ax[0].set_ylabel(r"$-Im(Z) / \Omega$") ax[0].axis("equal") - if label is not None: ax[0].legend() @@ -214,7 +210,7 @@ def plot_impedance_combo( if not isinstance(ax[1], list): ax[1] = [ax[1], ax[1].twinx()] # Create a twin axis if not already present plot_magnitude = getattr(ax[1][0], "scatter" if scatter else "plot") - magnitude_Z = np.sqrt(Re_Z**2 + Im_Z**2) # Calculate magnitude of Z + magnitude_Z = np.abs(Z) # Calculate magnitude of Z using np.abs plot_magnitude(freq, magnitude_Z, color="blue", label=r"$|Z|$", **kwargs) ax[1][0].set_xscale("log") ax[1][0].set_xlabel("Frequency (Hz)") @@ -223,17 +219,17 @@ def plot_impedance_combo( # Bode plot (phase) Phase(Z) plot_phase = getattr(ax[1][1], "scatter" if scatter else "plot") - phase_Z = np.degrees(np.arctan2(Im_Z, Re_Z)) # Calculate phase of Z in degrees + phase_Z = np.degrees(np.angle(Z)) # Calculate phase of Z in degrees using np.angle plot_phase(freq, phase_Z, color="red", label=r"$\text{Phase}(Z)$", **kwargs) ax[1][1].set_ylabel(r"$\text{Phase}(Z) \, (\degree)$") ax[1][1].yaxis.label.set_color("red") - # No grid lines for the second y-axis as the primary y-axis already has them ax[1][1].grid(False) fig.tight_layout() return fig, ax + def plot_linKK_residuals( freq: np.ndarray[float], res_real: np.ndarray[float], From 2776ac4031aa4a0d5a1ca3a7bf60bac0a25e66db Mon Sep 17 00:00:00 2001 From: Amin Sadeghi Date: Sun, 14 Apr 2024 22:26:41 -0400 Subject: [PATCH 3/3] Factor act Bode plot as standalone function --- src/autoeis/visualization.py | 124 +++++++++++++++++++++++------------ 1 file changed, 82 insertions(+), 42 deletions(-) diff --git a/src/autoeis/visualization.py b/src/autoeis/visualization.py index 171f9cf0..284dc754 100644 --- a/src/autoeis/visualization.py +++ b/src/autoeis/visualization.py @@ -7,6 +7,8 @@ :toctree: generated/ draw_circuit + plot_bode + plot_nyquist plot_impedance_combo plot_linKK_residuals plot_nyquist @@ -36,6 +38,8 @@ __all__ = [ "draw_circuit", + "plot_bode", + "plot_nyquist", "plot_impedance_combo", "plot_linKK_residuals", "plot_nyquist", @@ -111,7 +115,7 @@ def draw_circuit(circuit: str) -> mpl.figure.Figure: def plot_nyquist( Z: np.ndarray[complex], fmt: str = "o-", - size: int = 4, + markersize: int = 6, color: str = None, alpha: int = 1, label: str = None, @@ -125,8 +129,8 @@ def plot_nyquist( Impedance data. fmt: str, optional Format of the markers in the plot. Default is "o-". - size: int, optional - Size of the markers in the plot. Default is 4. + markersize: int, optional + Size of the markers in the plot. Default is 6. color: str, optional Color of the markers in the plot. Default is None. alpha: int, optional @@ -149,7 +153,7 @@ def plot_nyquist( color = fmt[0] fmt = fmt[1:] - ax.plot(Z.real, -Z.imag, fmt, c=color, markersize=size, label=label, alpha=alpha) + ax.plot(Z.real, -Z.imag, fmt, c=color, markersize=markersize, label=label, alpha=alpha) ax.set_xlabel("Re(Z)") ax.set_ylabel("-Im(Z)") ax.axis("equal") @@ -160,13 +164,73 @@ def plot_nyquist( return ax.figure, ax +def plot_bode( + freq: np.ndarray[float], + Z: np.ndarray[complex], + fmt=".-", + markersize=6, + deg: bool = True, + ax: plt.Axes = None, +) -> tuple[plt.Figure, plt.Axes]: + """Plots the Bode plot for the impedance data. + + Parameters + ---------- + freq: np.ndarray[float] + Frequencies corresponding to the impedance data. + Z: np.ndarray[complex] + Impedance data. + fmt: str, optional + Format of the markers in the plot. Default is ".-". + markersize: int, optional + Size of the markers in the plot. Default is 6. + deg: bool, optional + If True, plots the Bode plot in degrees. Default is True. + ax: plt.Axes, optional + Axes to plot on. Default is None. + + Returns + ------- + tuple[plt.Figure, plt.Axes] + Figure and axes of the plot. + """ + if ax is None: + fig, ax = plt.subplots(figsize=(5.5, 3.5)) + + ax.plot(freq, np.abs(Z), fmt, label=r"$|Z|$", markersize=markersize) + ax.set_xscale("log") + ax2 = ax.twinx() + ax2.plot( + freq, np.angle(Z, deg=deg), fmt, markersize=markersize, color="b", label=r"$\phi$" + ) + ax.set_xlabel("frequency (Hz)") + ax.set_ylabel(r"$|Z|$") + ax2.set_ylabel(rf"$\phi$ ({('deg' if deg else 'rad')})") + + # Color y-axes for better visibility + ax2.yaxis.label.set_color("b") + for label in ax2.get_yticklabels(): + label.set_color("b") + ax.yaxis.label.set_color("r") + for label in ax.get_yticklabels(): + label.set_color("r") + # Avoid overlapping tick labels from ax2 on top of ax + ax2.grid(False) + # Combine legends + lines1, labels1 = ax.get_legend_handles_labels() + lines2, labels2 = ax2.get_legend_handles_labels() + ax.legend(lines1 + lines2, labels1 + labels2, loc="upper center") + + return ax.figure, [ax, ax2] + + def plot_impedance_combo( freq: np.ndarray, Z: np.ndarray, - size: int = 10, + fmt: str = ".-", + markersize: int = 6, ax: list[plt.Axes] = None, - scatter: bool = True, - label: str = None + label: str = None, ) -> tuple[plt.Figure, list[plt.Axes]]: """Plots EIS data in Nyquist and Bode plots. @@ -176,12 +240,12 @@ def plot_impedance_combo( Frequencies corresponding to the impedance data. Z: np.ndarray[complex] Impedance data. - size: int, optional + fmt: str, optional + Format of the markers in the plot. Default is ".-". + markersize: int, optional Size of the markers in the plots. Default is 10. ax: list[plt.Axes], optional List of axes (must be of length 2) to plot on. Default is None. - scatter: bool, optional - If True, plots the data as scatter plots. Default is True. label: str, optional Label for the plot. Default is None. @@ -191,45 +255,21 @@ def plot_impedance_combo( Figure and axes of the plots. """ if ax is None: - fig, ax = plt.subplots(ncols=2, figsize=(8, 3)) + fig, ax = plt.subplots(ncols=2, figsize=(9, 3.5)) else: - assert len(ax) == 2 and all(isinstance(a, Axes) for a in ax), "Incompatible 'ax'. Use plt.subplots(ncols=2)" + msg = "Incompatible 'ax'. Use plt.subplots(ncols=2)" + assert len(ax) == 2 and all(isinstance(a, Axes) for a in ax), msg fig = ax[0].figure - # Nyquist plot - plot = getattr(ax[0], "scatter" if scatter else "plot") - kwargs = {"s": size} if scatter else {} - plot(Z.real, -Z.imag, label=label, **kwargs) - ax[0].set_xlabel(r"$Re(Z) / \Omega$") - ax[0].set_ylabel(r"$-Im(Z) / \Omega$") - ax[0].axis("equal") - if label is not None: - ax[0].legend() - - # Bode plot (magnitude) |Z| - if not isinstance(ax[1], list): - ax[1] = [ax[1], ax[1].twinx()] # Create a twin axis if not already present - plot_magnitude = getattr(ax[1][0], "scatter" if scatter else "plot") - magnitude_Z = np.abs(Z) # Calculate magnitude of Z using np.abs - plot_magnitude(freq, magnitude_Z, color="blue", label=r"$|Z|$", **kwargs) - ax[1][0].set_xscale("log") - ax[1][0].set_xlabel("Frequency (Hz)") - ax[1][0].set_ylabel(r"$|Z| / \Omega$") - ax[1][0].yaxis.label.set_color("blue") - - # Bode plot (phase) Phase(Z) - plot_phase = getattr(ax[1][1], "scatter" if scatter else "plot") - phase_Z = np.degrees(np.angle(Z)) # Calculate phase of Z in degrees using np.angle - plot_phase(freq, phase_Z, color="red", label=r"$\text{Phase}(Z)$", **kwargs) - ax[1][1].set_ylabel(r"$\text{Phase}(Z) \, (\degree)$") - ax[1][1].yaxis.label.set_color("red") - ax[1][1].grid(False) + plot_nyquist(Z=Z, label=label, ax=ax[0], fmt=fmt, markersize=markersize) + plot_bode(freq, Z, ax=ax[1], fmt=fmt, markersize=markersize) + ax[0].set_title("Nyquist plot") + ax[1].set_title("Bode plot") fig.tight_layout() return fig, ax - def plot_linKK_residuals( freq: np.ndarray[float], res_real: np.ndarray[float], @@ -258,7 +298,7 @@ def plot_linKK_residuals( fig, ax = plt.subplots(figsize=(5.5, 3.5)) ax.plot(freq, res_real, label="delta Re") ax.plot(freq, res_imag, label="delta Im") - ax.set_xlabel("freq (Hz)") + ax.set_xlabel("frequency (Hz)") ax.set_ylabel("delta %") ax.set_xscale("log") ax.set_title("Lin-KK validation")