Step 4: Plot The Reconstructed Orbits

%matplotlib ipympl
import plotly.express as px
import plotly.graph_objects as go
import polars as pl
import ipywidgets as widgets
import matplotlib.pyplot as plt
import numpy as np
import plotly.io as pio
pio.renderers.default = "notebook"  # or "browser"

Data loading and preprocessing

subharmonics_colors = {
    0: "#BAC24C",
    1: "#5179D6",
    2: "#2ca02c",
    3: "#d62728",
    4: "#9467bd",
    5: "#9651D6",
}
orbits = pl.read_parquet("outputs/orbits.parquet")
orbits
shape: (58, 19)
xdotxvEvxww0AdQfdalphaC0RLMdetected_subharmonictarget_frequencyorbit_labelattractor_labelEh
f64f64f64f64f64f64f64f64f64f64f64f64f64f64i64f64i64i64f64
-0.0002650.293995-3.1019330.00.0005121.02.587.035.00.0680.0000017830.00.0250.0173135.0000.000021
0.0006830.0503240.6229150.00.0005121.02.587.044.00.0680.0000017830.00.0250.0173344.0110.000001
0.000031-0.068758-0.6517650.00.0005121.02.587.044.00.0680.0000017830.00.0250.0173344.0128.7274e-7
-0.000750.0447380.0069360.00.0005121.02.587.044.00.0680.0000017830.00.0250.0173344.0137.6701e-7
-0.000499-0.0093330.0249620.00.0005121.02.587.050.00.0680.0000017830.00.0250.0173150.0246.5385e-9
0.0007010.0568280.6543250.00.0005121.02.587.047.00.0680.0000017830.00.0250.0173347.030530.000001
-0.0002070.247926-2.6556090.00.0005121.02.587.032.00.0680.0000017830.00.0250.0173132.031540.000017
-0.0000630.102962-1.1714360.00.0005121.02.587.020.00.0680.0000017830.00.0250.0173120.032550.000007
-0.000564-0.0289660.3250050.00.0005121.02.587.038.00.0680.0000017830.00.0250.0173238.033562.9101e-7
-0.000347-0.002615-0.1813970.00.0005121.02.587.038.00.0680.0000017830.00.0250.0173238.033574.3629e-8
orbit_data = pl.read_parquet("outputs/orbit_data.parquet")
orbit_data.sort("Ph", descending=True)
shape: (34, 5)
orbit_labelfddetected_subharmonicEhPh
i64f64i64f64f64
450.010.0000530.002674
1047.010.0000450.002125
1944.010.0000380.001669
2841.010.0000320.001293
2938.010.0000260.000987
1844.011.3140e-85.7818e-7
847.019.1480e-94.2996e-7
947.019.1450e-94.2982e-7
250.016.5385e-93.2693e-7
350.016.5373e-93.2687e-7
power_data = orbit_data.with_columns(
    (pl.col("Ph") * 1000.0).alias("Ph_scaled"),
    pl.col("detected_subharmonic").cast(pl.Utf8).alias("detected_subharmonic_label"),
)

fig = px.scatter(
    power_data.to_pandas(),
    x="fd",
    y="Ph_scaled",
    color="detected_subharmonic_label",
    symbol="detected_subharmonic_label",
    color_discrete_map={str(key): value for key, value in subharmonics_colors.items()},
    hover_data=["orbit_label", "fd", "Ph", "detected_subharmonic"],
    log_y=True,
    title="Damping dissipation vs. Drive Frequency",
    labels={
        "fd": "Drive Frequency [Hz]",
        "Ph_scaled": "Harvested Power [mW]",
        "detected_subharmonic_label": "Detected subharmonic",
    },
)
fig.update_traces(marker=dict(size=10))
fig.update_layout(width=1000, height=700)
fig

Orbits a given frequency

fd = 50.0
x = "x"
y = "dotx"
z = "v"
xlabel = "Position, x"
ylabel = "Speed, dot x"
zlabel = "Voltage, v"

orbits_fd = orbits.filter(pl.col("fd") == fd)
orbits_data_fd = orbit_data.filter(pl.col("fd") == fd)
orbits_dic = {}
for row in orbits_fd.iter_rows(named=True):
    olabel = row["orbit_label"]
    alabel = row["attractor_label"]
    if olabel not in orbits_dic.keys():
        orbits_dic[olabel] = {"orbit": {}, "data": row}
    odata = pl.read_parquet(
        f"outputs/orbits_from_attractors/orbit_{olabel}_attractor_{alabel}.parquet"
    )
    orbits_dic[olabel]["orbit"][alabel] = odata


fig = go.Figure()

for ok, odata in orbits_dic.items():
    xv = []
    yv = []
    zv = []
    name = ""
    sh = odata["data"]["detected_subharmonic"]
    if sh == 1:
        name += "H"
    else:
        name += f"SH"
    name += f"{sh}_id{ok}"
    for ak, adata in odata["orbit"].items():
        orbit = odata["orbit"][ak].to_pandas()    
        xv.append(orbit[x].values)
        yv.append(orbit[y].values)
        zv.append(orbit[z].values)
    xv = np.concatenate(xv)
    yv = np.concatenate(yv)
    zv = np.concatenate(zv)
    Np = len(xv) // sh
    fig.add_trace(go.Scatter3d(
            x=xv,
            y=yv,
            z=zv,
            mode="lines",
            legendgroup=f"g{ok}",
            line=dict(
                color=subharmonics_colors[odata["data"]["detected_subharmonic"]], width=4
            ),
            name=name,
        )
    )
    fig.add_trace(go.Scatter3d(
            x=xv[::Np],
            y=yv[::Np],
            z=zv[::Np],
            mode="markers",
            legendgroup=f"g{ok}",
            marker=dict(symbol="circle", color=subharmonics_colors[odata["data"]["detected_subharmonic"]], size=3),
            showlegend=False
        )
    )

fig.update_layout(
    title=f"Orbits and attractors at Drive Frequency fd={fd} Hz",
    legend=dict(
        title="Orbits and attractors",
        x=0.02, y=0.98,
        bgcolor="rgba(255,255,255,0.7)"
    ),
    margin=dict(l=0, r=0, t=50, b=0),

    scene=dict(
        xaxis=dict(
            title=xlabel,
            showgrid=True,
            gridcolor="rgba(0,0,0,0.15)",
            zeroline=True,
            zerolinecolor="rgba(0,0,0,0.25)",
            showbackground=True,
            backgroundcolor="rgba(245,245,245,1)",
            ticks="outside",
        ),
        yaxis=dict(
            title=ylabel,
            showgrid=True,
            gridcolor="rgba(0,0,0,0.15)",
            zeroline=True,
            showbackground=True,
            backgroundcolor="rgba(245,245,245,1)",
        ),
        zaxis=dict(
            title=zlabel,
            showgrid=True,
            gridcolor="rgba(0,0,0,0.15)",
            zeroline=True,
            showbackground=True,
            backgroundcolor="rgba(245,245,245,1)",
        ),

        # Keep scales comparable (optional)
        aspectmode="cube",     # or "data" / "manual"
        # aspectratio=dict(x=1, y=1, z=0.6),

        # Initial view (optional)
        camera=dict(
            eye=dict(x=1.6, y=1.6, z=1.1)
        )
    )
)

fig.update_layout(
    width=1000,
    height=1000,
)
fig.show()

Orbits as a function of frequency

x = "x"
y = "dotx"
xlabel = "Position, x"
ylabel = "Speed, dot x"
zlabel = "Drive Frequency, fd [Hz]"


orbits_dic = {}
for row in orbits.iter_rows(named=True):
    olabel = row["orbit_label"]
    alabel = row["attractor_label"]
    if olabel not in orbits_dic.keys():
        orbits_dic[olabel] = {"orbit": {}, "data": row}
    odata = pl.read_parquet(
        f"outputs/orbits_from_attractors/orbit_{olabel}_attractor_{alabel}.parquet"
    )
    orbits_dic[olabel]["orbit"][alabel] = odata


fig = go.Figure()

show_legend = set()
for ok, odata in orbits_dic.items():
    xv = []
    yv = []
   
    name = ""
    sh = odata["data"]["detected_subharmonic"]
    fd = odata["data"]["fd"]
    if sh == 1:
        name += "H"
    else:
        name += f"SH"
    name += f"{sh}"
    for ak, adata in odata["orbit"].items():
        orbit = odata["orbit"][ak].to_pandas()    
        xv.append(orbit[x].values)
        yv.append(orbit[y].values)
    xv = np.concatenate(xv)
    yv = np.concatenate(yv)
    Np = len(xv) // sh
    zv = np.ones_like(xv) * fd
    oname = name + "_orbit"
    aname = name + "_attractor"
    fig.add_trace(go.Scatter3d(
            x=xv,
            y=yv,
            z=zv,
            mode="lines",
            legendgroup=oname,
            line=dict(
                color=subharmonics_colors[odata["data"]["detected_subharmonic"]], width=4
            ),
            name=oname,
            showlegend=oname not in show_legend,
        )
    )   
    show_legend.add(oname)
    fig.add_trace(go.Scatter3d(
            x=xv[::Np],
            y=yv[::Np],
            z=zv[::Np],
            mode="markers",
            legendgroup=aname,
            name =aname,
            marker=dict(symbol="circle", color=subharmonics_colors[odata["data"]["detected_subharmonic"]], size=3),
            showlegend=aname not in show_legend
        )
    )
    show_legend.add(aname)

fig.update_layout(
    title=f"Orbits and attractors at Drive Frequency fd={fd} Hz",
    legend=dict(
        title="Orbits and attractors",
        x=0.02, y=0.98,
        bgcolor="rgba(255,255,255,0.7)"
    ),
    margin=dict(l=0, r=0, t=50, b=0),

    scene=dict(
        xaxis=dict(
            title=xlabel,
            showgrid=True,
            gridcolor="rgba(0,0,0,0.15)",
            zeroline=True,
            zerolinecolor="rgba(0,0,0,0.25)",
            showbackground=True,
            backgroundcolor="rgba(245,245,245,1)",
            ticks="outside",
        ),
        yaxis=dict(
            title=ylabel,
            showgrid=True,
            gridcolor="rgba(0,0,0,0.15)",
            zeroline=True,
            showbackground=True,
            backgroundcolor="rgba(245,245,245,1)",
        ),
        zaxis=dict(
            title=zlabel,
            showgrid=True,
            gridcolor="rgba(0,0,0,0.15)",
            zeroline=True,
            showbackground=True,
            backgroundcolor="rgba(245,245,245,1)",
        ),

        # Keep scales comparable (optional)
        aspectmode="cube",     # or "data" / "manual"
        # aspectratio=dict(x=1, y=1, z=0.6),

        # Initial view (optional)
        camera=dict(
            eye=dict(x=1.6, y=1.6, z=1.1)
        )
    )
)

fig.update_layout(
    width=1000,
    height=700,
)
fig.show()