HOPSFlow-Paper/plot_utils.py

794 lines
21 KiB
Python
Raw Permalink Normal View History

2022-11-30 18:24:23 -05:00
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
from contextlib import contextmanager
import utilities as ut
from hopsflow.util import EnsembleValue
from hops.util.utilities import (
relative_entropy,
relative_entropy_single,
entropy,
trace_distance,
)
from matplotlib.gridspec import GridSpec
try:
import hiro_models.model_auxiliary as aux
except:
aux = None
def cm2inch(*tupl):
inch = 2.54
if isinstance(tupl[0], tuple):
return tuple(i / inch for i in tupl[0])
else:
return tuple(i / inch for i in tupl)
###############################################################################
# Plot Porn #
###############################################################################
def wrap_plot(f):
def wrapped(*args, ax=None, setup_function=plt.subplots, **kwargs):
fig = None
if not ax:
fig, ax = setup_function()
ret_val = f(*args, ax=ax, **kwargs)
return (fig, ax, ret_val) if ret_val else (fig, ax)
return wrapped
def get_figsize(width="thesis", fraction=1, subplots=(1, 1)):
"""Set figure dimensions to avoid scaling in LaTeX.
Parameters
----------
width: float or string
Document width in points, or string of predined document type
fraction: float, optional
Fraction of the width which you wish the figure to occupy
subplots: array-like, optional
The number of rows and columns of subplots.
Returns
-------
fig_dim: tuple
Dimensions of figure in inches
"""
if width == "thesis":
width_pt = 330.62111
elif width == "poster":
width_pt = 957.13617
elif width == "beamer":
width_pt = 307.28987
else:
width_pt = width
# Width of figure (in pts)
fig_width_pt = width_pt * fraction
# Convert from pt to inches
inches_per_pt = 1 / 72.27
# Golden ratio to set aesthetic figure height
# https://disq.us/p/2940ij3
golden_ratio = (5 ** 0.5 - 1) / 2
# Figure width in inches
fig_width_in = fig_width_pt * inches_per_pt
# Figure height in inches
fig_height_in = fig_width_in * golden_ratio * (subplots[0] / subplots[1])
return (fig_width_in, fig_height_in)
# def get_figsize(
# columnwidth: float,
# wf: float = 0.5,
# hf: float = (5.0 ** 0.5 - 1.0) / 2.0,
# ) -> tuple[float, float]:
# """
# :param wf: Width fraction in columnwidth units.
# :param hf: Weight fraction in columnwidth units.
# Set by default to golden ratio.
# :param columnwidth: width of the column in latex.
# Get this from LaTeX using \showthe\columnwidth
# :returns: The ``[fig_width,fig_height]`` that should be given to
# matplotlib.
# """
# fig_width_pt = columnwidth * wf
# inches_per_pt = 1.0 / 72.27 # Convert pt to inch
# fig_width = fig_width_pt * inches_per_pt # width in inches
# fig_height = fig_width * hf # height in inches
# return [fig_width, fig_height]
@wrap_plot
def plot_complex(x, y, *args, ax=None, label="", absolute=False, **kwargs):
label = label + ", " if (len(label) > 0) else ""
ax.plot(
x,
abs(y.real) if absolute else y.real,
*args,
label=f"{label}{'absolute' if absolute else ''} real part",
**kwargs,
)
ax.plot(
x,
abs(y.imag) if absolute else y.imag,
*args,
label=f"{label}{'absolute' if absolute else ''} imag part",
**kwargs,
)
ax.legend()
@wrap_plot
def plot_convergence(
x,
y,
reference=None,
ax=None,
label="",
slice=None,
linestyle="-",
bath=None,
):
if bath is not None:
y = y.for_bath(bath)
label = label + ", " if (len(label) > 0) else ""
slice = (0, -1) if not slice else slice
y_final = y[-1]
for i in range(len(y) - 1 if reference is None else len(y)):
current_value = y[i]
consistency = current_value.consistency(
y_final if reference is None else reference
)
line = ax.plot(
x,
current_value.value,
label=f"{label}$N={current_value.N}$ $({consistency:.1f}\%)$",
alpha=current_value.N / y.N,
linestyle=linestyle if i == (len(y) - 1) else ":",
)
if reference is None:
plot_with_σ(
x,
y_final,
label=f"{label}$N={y.N}$",
linestyle=linestyle,
ax=ax,
)
else:
ax.fill_between(
x,
y_final.value - y_final.σ,
y_final.value + y_final.σ,
color=lighten_color(line[0].get_color(), 0.5),
)
return None
def lighten_color(color, amount=0.5):
"""
Lightens the given color by multiplying (1-luminosity) by the given amount.
Input can be matplotlib color string, hex string, or RGB tuple.
Examples:
>> lighten_color('g', 0.3)
>> lighten_color('#F034A3', 0.6)
>> lighten_color((.3,.55,.1), 0.5)
"""
import matplotlib.colors as mc
import colorsys
try:
c = mc.cnames[color]
except:
c = color
c = colorsys.rgb_to_hls(*mc.to_rgb(c))
return colorsys.hls_to_rgb(c[0], 1 - amount * (1 - c[1]), c[2])
def fancy_error(x, y, err, ax, **kwargs):
line = ax.plot(
x,
y,
**kwargs,
)
err = ax.fill_between(
x,
y + err,
y - err,
color=lighten_color(line[0].get_color(), 0.5),
alpha=0.5,
)
return line, err
@wrap_plot
def plot_with_σ(
x,
y,
ax=None,
transform=lambda y: y,
bath=None,
strobe_frequency=None,
strobe_tolerance=1e-3,
2023-07-24 15:23:11 -04:00
strobe_data=None,
2022-11-30 18:24:23 -05:00
hybrid=False,
**kwargs,
):
err = (y.σ[bath] if bath is not None else y.σ).real
y_final = transform(y.value[bath] if bath is not None else y.value)
2023-07-24 15:23:11 -04:00
strobe_mode = strobe_frequency is not None or strobe_data
2022-11-30 18:24:23 -05:00
strobe_indices = None
strobe_times = None
strobe_style = dict(linestyle="none", marker="o", markersize=2) | kwargs
if strobe_mode:
2023-07-24 15:23:11 -04:00
if strobe_data:
strobe_times, strobe_indices = strobe_data
else:
strobe_times, strobe_indices = ut.strobe_times(
x, strobe_frequency, strobe_tolerance
)
2022-11-30 18:24:23 -05:00
line = ax.errorbar(
strobe_times,
y_final[strobe_indices],
err[strobe_indices],
**strobe_style,
)
if not hybrid:
return line
kwargs["color"] = lighten_color(line[0].get_color(), 0.5)
kwargs["label"] = ""
return fancy_error(x, y_final, err, ax=ax, **kwargs)
@wrap_plot
def plot_diff_vs_sigma(
x,
y,
reference,
ax=None,
label="",
transform=lambda y: y,
ecolor="yellow",
ealpha=0.5,
ylabel=None,
bath=None,
):
label = label + ", " if (len(label) > 0) else ""
if bath is not None:
y = y.for_bath(bath)
reference = reference.for_bath(bath)
ax.fill_between(
x,
0,
reference.σ,
color=ecolor,
alpha=ealpha,
label=rf"{label}$\sigma\, (N={reference.N})$",
)
for i in range(len(y)):
current = y[i]
not_last = current.N < y[-1].N
consistency = current.consistency(reference)
diff = abs(current - reference)
ax.plot(
x,
diff.value,
label=rf"{label}$N={current.N}$ $({consistency:.1f}\%)$",
alpha=consistency / 100 if not_last else 1,
linestyle=":" if not_last else "-",
color=None if not_last else "red",
)
if ylabel is not None:
if ylabel[0] == "$":
ylabel = ylabel[1:-1]
else:
ylabel = rf"\text{{ {ylabel} }}"
ax.set_ylabel(rf"$|{{{ylabel}}}_{{\mathrm{{ref}}}}-{{{ylabel}}}_{{N_i}}|$")
def plot_interaction_consistency(
models,
reference=None,
label_fn=lambda model: f"$ω_c={model.ω_c:.2f}$",
inset=None,
bath=0,
**kwargs,
):
fig, (ax, ax2) = plt.subplots(ncols=2)
ax3 = None
if inset:
slc, bounds = inset
ax3 = ax.inset_axes(bounds)
if reference:
with aux.get_data(reference) as data:
reference_energy = reference.interaction_energy(data, **kwargs)
for model in models:
with aux.get_data(model) as data:
energy = model.interaction_energy(data, **kwargs).for_bath(bath)
interaction_ref = model.interaction_energy_from_conservation(
data, **kwargs
).for_bath(bath)
self_consistency = energy.consistency(interaction_ref)
normalizer = 1 / abs(energy.value).max()
if reference:
final_consistency = reference_energy.consistency(interaction_ref)
_, _, (line, _) = plot_with_σ(
data.time[:],
energy,
ax=ax,
)
plot_with_σ(
data.time[:],
interaction_ref,
ax=ax,
linestyle="--",
color=lighten_color(line[0].get_color(), 0.8),
)
plot_with_σ(
data.time[:],
(energy - interaction_ref) * normalizer,
label=label_fn(model)
+ fr", (${self_consistency:.0f}\%$"
+ (fr", ${final_consistency:.0f}\%$)" if reference else ")"),
ax=ax2,
color=line[0].get_color(),
)
if inset:
plot_with_σ(
data.time[slc],
energy.slice(slc),
ax=ax3,
color=line[0].get_color(),
)
plot_with_σ(
data.time[slc],
interaction_ref.slice(slc),
ax=ax3,
linestyle="--",
color=lighten_color(line[0].get_color(), 0.8),
)
ax.set_xlabel(r"$\tau$")
ax2.set_xlabel(r"$\tau$")
ax.set_ylabel(r"$\langle H_\mathrm{I}\rangle$")
ax2.set_ylabel(
r"$\langle (H_\mathrm{I}\rangle - \langle H_\mathrm{I}\rangle_\mathrm{ref}) / \langle H_\mathrm{I}\rangle_\mathrm{max}$"
)
ax2.legend()
return fig, (ax, ax2, ax3)
def plot_interaction_consistency_development(
models,
reference=None,
label_fn=lambda model: rf"$\omega_c={model.ω_c:.2f}$",
**kwargs,
):
fig = plt.figure(constrained_layout=True)
gs = GridSpec(2, 2, figure=fig)
ax = fig.add_subplot(gs[0, :])
ax2 = fig.add_subplot(gs[1, 0])
ax3 = fig.add_subplot(gs[1, 1])
ax2.set_xscale("log")
ax2.set_yscale("log")
ax3.set_xscale("log")
ax3.set_yscale("log")
if reference is not None:
with aux.get_data(reference) as data:
reference_energy = reference.interaction_energy(data, **kwargs)
for model in models:
with aux.get_data(model) as data:
interaction_ref = model.interaction_energy_from_conservation(data, **kwargs)
if reference is not None:
energy = reference_energy
else:
energy = model.interaction_energy(data, **kwargs)
normalizer = 1 / abs(energy.value).max()
diff = abs(interaction_ref - energy) * normalizer
ns, values, σs, diffs = [], [], [], []
for N, val, σ in diff.aggregate_iterator:
ns.append(N)
values.append((val < σ).sum() / len(val[0]) * 100)
σs.append(σ.mean() * normalizer)
diffs.append(val.mean() * normalizer)
σ_ref, σ_int = [], []
for _, _, σ in interaction_ref.aggregate_iterator:
σ_ref.append(σ.mean() * normalizer)
for _, _, σ in energy.aggregate_iterator:
σ_int.append(σ.mean() * normalizer)
lines = ax.plot(
ns,
values,
linestyle="--",
marker=".",
markersize=2,
label=label_fn(model),
)
ax2.plot(
ns,
σs,
label=label_fn(model),
color=lines[0].get_color(),
)
ax2.plot(
ns,
diffs,
label=label_fn(model),
linestyle="--",
color=lines[0].get_color(),
)
ax3.plot(
ns,
σ_ref,
label=label_fn(model) + " (from conservation)",
linestyle="--",
color=lines[0].get_color(),
)
ax3.plot(ns, σ_int, label=label_fn(model) + " (direct)")
ax.axhline(68, linestyle="-.", color="grey", alpha=0.5)
ax.set_xlabel("$N$")
ax.set_title("Consistency")
ax2.set_xlabel("$N$")
ax3.set_xlabel("$N$")
ax2.set_ylabel(r"$[|\langle H_I\rangle|_\mathrm{max}]$")
ax2.set_title(r"Mean Actual vs. Allowed deviation.")
ax3.set_ylabel(r"$[|\langle H_I\rangle|_\mathrm{max}]$")
ax3.set_title(r"Statistical Error of $\langle H_I\rangle$.")
ax.set_ylabel(("" if reference else "Self-") + r"Consistency [$\%$]")
ax.legend()
straight_line = matplotlib.lines.Line2D(
[], [], color="black", label=r"$\langle \sigma_\Delta\rangle$"
)
dashed_line = matplotlib.lines.Line2D(
[], [], color="black", label=r"$\langle\Delta\rangle$", linestyle="--"
)
ax2.legend(handles=[straight_line, dashed_line])
straight_line = matplotlib.lines.Line2D([], [], color="black", label=r"Direct")
dashed_line = matplotlib.lines.Line2D(
[], [], color="black", label=r"Energy Conservation", linestyle="--"
)
ax3.legend(handles=[dashed_line, straight_line])
return fig, [ax, ax2, ax3]
def plot_consistency_development(value, reference, fig=None):
if fig:
(ax, ax2) = fig.subplots(nrows=2, sharex=True)
else:
fig, (ax, ax2) = (fig or plt).subplots(nrows=2, sharex=True)
ax.set_xscale("log")
ax2.set_xscale("log")
ax2.set_yscale("log")
diff = abs(value - reference)
ns, values, σs, max_diff = [], [], [], []
for N, val, σ in diff.aggregate_iterator:
ns.append(N)
values.append(((val < σ).sum() / len(val[0])) * 100)
σs.append(σ.mean())
max_diff.append(val.mean())
values = np.array(values)
ns = np.array(ns)
where_consistent = values >= 68
values_consistent = values.copy()
values_consistent[~where_consistent] = np.nan
values_inconsistent = values.copy()
values_inconsistent[where_consistent] = np.nan
ax.plot(
ns,
values_consistent,
linestyle="none",
marker=".",
markersize=5,
)
ax.plot(
ns,
values_inconsistent,
linestyle="none",
marker=".",
markersize=5,
)
ax2.plot(ns, np.array(σs) / abs(reference).max(), label=r"$\langle \sigma\rangle$")
ax2.plot(
ns, np.array(max_diff) / abs(reference).max(), label=r"$\langle \Delta\rangle$"
)
ax.axhline(68, linestyle="-.", color="grey", alpha=0.5)
ax2.set_xlabel("$N$")
ax.set_ylabel(r"[$\%$]")
ax.set_title(r"Consistency")
ax2.set_ylabel(r"$[{J_\mathrm{max}}]$")
ax2.set_title(r"Deviation")
ax2.legend()
return fig, [ax, ax2]
def plot_flow_bcf(models, label_fn=lambda model: f"$ω_c={model.ω_c:.2f}$", **kwargs):
fig, ax = plt.subplots()
for model in models:
with aux.get_data(model) as data:
flow = model.bath_energy_flow(data, **kwargs)
_, _, (line, _) = plot_with_σ(
data.time[:],
flow,
ax=ax,
label=label_fn(model),
bath=0,
transform=lambda y: -y,
)
ax.plot(
data.time[:],
-model.L_expect * model.bcf_scale * model.bcf(data.time[:]).imag,
linestyle="--",
color=line[0].get_color(),
)
return fig, ax
def plot_energy_overview(
model,
ensemble_args=None,
online=True,
# system=True,
# bath=True,
# total=True,
# flow=True,
# interaction=True,
bath_names=None,
**kwargs,
):
if not ensemble_args:
ensemble_args = {}
fig, ax = plt.subplots()
ax.set_ylabel("Energy")
ax.set_xlabel(r"$\tau$")
data = None if online else aux.get_data(model)
system_energy = model.system_energy(data, **ensemble_args)
bath_energy = model.bath_energy(data, **ensemble_args)
interaction_energy = model.interaction_energy(data, **ensemble_args)
# flow = model.bath_energy_flow(data, **ensemble_args)
2023-03-20 10:59:50 -04:00
plot_with_σ(
model.t,
system_energy,
ax=ax,
label=r"$\langle{H_\mathrm{sys}}\rangle$",
**kwargs,
)
2022-11-30 18:24:23 -05:00
num_baths = interaction_energy.num_baths
for bath in range(num_baths):
label = bath_names[bath] if bath_names else bath + 1
# plot_with_σ(
# model.t, flow, bath=bath, ax=ax, label=f"Flow {bath+1}", **kwargs
# )
plot_with_σ(
2023-03-20 10:59:50 -04:00
model.t,
bath_energy,
bath=bath,
ax=ax,
label=fr"$\Delta \langle H_{{\mathrm{{B}}}}\rangle$ ({label})",
**kwargs,
2022-11-30 18:24:23 -05:00
)
plot_with_σ(
model.t,
interaction_energy,
ax=ax,
bath=bath,
2023-03-20 10:59:50 -04:00
label=rf"$\langle H_I\rangle$ ({label})",
2022-11-30 18:24:23 -05:00
**kwargs,
)
total = model.total_energy_from_power(data, **ensemble_args)
plot_with_σ(
model.t,
total,
ax=ax,
2023-03-20 10:59:50 -04:00
label=r"$\Delta \langle H\rangle$",
2022-11-30 18:24:23 -05:00
linestyle="--",
**kwargs,
)
if data:
data.close()
return (fig, ax)
@wrap_plot
def plot_coherences(model, ax=None):
with aux.get_data(model) as data:
plot_with_σ(
model.t,
EnsembleValue(
(
0,
np.abs(np.array(data.rho_t_accum.mean)[:, 0, 1]),
np.array(data.rho_t_accum.ensemble_std)[:, 0, 1],
)
),
ax=ax,
)
@wrap_plot
def plot_distance_measures(model, strobe_indices, ax=None):
with aux.get_data(model) as data:
plot_with_σ(
model.t, EnsembleValue(relative_entropy(data, strobe_indices[-1])), ax=ax
)
plot_with_σ(
model.t, EnsembleValue(trace_distance(data, strobe_indices[-1])), ax=ax
)
@wrap_plot
def plot_σ_development(ensemble_value, ax=None, **kwargs):
# norm = abs(ensemble_value.value).max()
ax.plot(
ensemble_value.Ns,
[σ.mean() for σ in ensemble_value.σs],
marker=".",
markersize=6,
linestyle="dotted",
**kwargs,
)
ax.set_ylabel("mean normalized $σ$")
ax.set_xlabel("$N$")
def plot_multi_energy_overview(
models,
label_fn=lambda m: fr"$\omega_c={m.ω_c:.1f},\,\alpha(0)={m.bcf(0).real:.2f}$",
ensemble_arg=dict(),
):
fig, ((ax_sys, ax_flow), (ax_inter, ax_bath)) = plt.subplots(
nrows=2, ncols=2, sharex=True
)
# flow_ins = ax_flow.inset_axes([0.4, 0.20, 0.5, 0.55])
ax_sys.set_ylabel(r"$\langle H_\mathrm{S}\rangle$")
ax_flow.set_ylabel("$J$")
ax_inter.set_ylabel(r"$\langle H_\mathrm{I}\rangle$")
ax_bath.set_ylabel(r"$\langle H_\mathrm{B}\rangle$")
ax_sys.set_xlabel(r"$\tau$")
ax_flow.set_xlabel(r"$\tau$")
ax_inter.set_xlabel(r"$\tau$")
ax_bath.set_xlabel(r"$\tau$")
handles = []
for model in models:
with aux.get_data(model) as data:
sys_energy = model.system_energy(data, **ensemble_arg)
flow = model.bath_energy_flow(data, **ensemble_arg).for_bath(0)
inter = model.interaction_energy(data, **ensemble_arg).for_bath(0)
bath = model.bath_energy(data, **ensemble_arg).for_bath(0)
_, _, (lines, _) = plot_with_σ(
data.time[:],
sys_energy,
ax=ax_sys,
label=label_fn(model),
)
handles.append(lines[0])
plot_with_σ(
data.time[:],
flow,
ax=ax_flow,
)
mask = np.logical_and(7 <= data.time[:], data.time[:] <= 9)
# plot_with_σ(
# data.time[mask],
# flow.slice(mask),
# ax=flow_ins,
# )
plot_with_σ(
data.time[:],
inter,
ax=ax_inter,
)
plot_with_σ(
data.time[:],
bath,
ax=ax_bath,
)
ax_sys.legend(
# ncol=2,
)
@wrap_plot
def plot_ρ(model, i, j, ax=None, **kwargs):
with aux.get_data(model) as data:
return plot_with_σ(
model.t,
EnsembleValue(
(data.rho_t_accum.mean[:, i, j], data.rho_t_accum.ensemble_std[:, i, j])
),
ax=ax,
**kwargs,
)