Ana içeriğe geç

Çizim Örnekleri

Genel Bakış

Bu sayfada, dynlib'in çizim araçlarının nasıl kullanılacağını gösteren demolar yer almaktadır. Bu araçlar, dynlib.plot API'si altında toplanmış olup; zaman serileri, faz portreleri, dönüş haritaları (return maps), vektör alanları, histogramlar ve animasyonlar oluşturmak için tutarlı ve basit bir arayüz sunar.

Zaman Serileri ve Faz Portreleri

Lojistik Harita Tanılamaları

Aşağıdaki örnek, yerleşik lojistik harita modelini oluşturur, bir başlangıç geçiş sürecini (transient) atladıktan sonra simülasyonu çalıştırır. Sonrasında series.plot, return_map ve cobweb yardımcı fonksiyonlarını kullanarak zaman serisini, dönüş haritasını ve örümcek ağı diyagramını (cobweb diagram) çizer. Ayrıca, analitik tahminleri sayısal olarak bulunan çekerlerle (attractors) karşılaştırmak için sim.model.fixed_points(seeds=...) ile bulunan sabit noktaları da ekrana yazar.

from dynlib import setup
from dynlib.plot import series, export, theme, fig, return_map, cobweb

model = '''
inline:
[model]
type="map"
name="Logistic Map"

[states]
x=0.1

[params]
r=4.0

[equations.rhs]
x = "r * x * (1 - x)"
'''

# stepper="map" is default and can be omitted for map models
sim = setup(model, stepper="map", jit=True, disk_cache=True)
sim.run(N=192, transient=40)
sim.run(resume=True, N=400)
res=sim.results()

seeds = [[0.1], [0.7], [0.9]]
fps = sim.model.fixed_points(seeds=seeds)
print(fps.points)

theme.use("notebook")
theme.update(grid=False)

# Create 1x2 grid for time series and return map
ax = fig.grid(rows=1, cols=2, size=(12, 5))

# Time series plot
series.plot(
    x=res.t,
    y=res["x"],
    style="line",
    ax=ax[0, 0],
    xlabel="n",
    ylabel="$x_n$",
    ylabel_rot=0,
    title="Logistic Map (r=4)",
    ypad=10,
    xlabel_fs=13,
    ylabel_fs=13,
    title_fs=14,
    xtick_fs=9,
    ytick_fs=11,
    lw=1.0
)

# Return map: x[n] vs x[n+1]
return_map(
    x=res["x"],
    step=1,
    style="scatter",
    ax=ax[0, 1],
    ms=2,
    color="C1",
    title="Return Map: $x[n]$ vs $x[n+1]$",
    xlabel_fs=13,
    ylabel_fs=13,
    title_fs=14,
    xtick_fs=9,
    ytick_fs=11,
)

cobweb(
    f=sim.model,
    x0=0.1,
    xlim=(0, 1),
    steps=50,
    color="green",
    stair_color="orange",
    identity_color="red",
)

export.show()

Van der Pol Osilatörü

Bu örnek, builtin://ode/vanderpol gibi katı (stiff) bir diferansiyel denklem modelinin tr-bdf2a gibi özel bir çözücü ile nasıl çalıştırılacağını gösterir. Simülasyon süresini dynlib.utils.Timer ile ölçer ve hem zaman serisini hem de faz portresini çizer. Bu problem \(\mu=1000\) parametre değeriyle diğer numerik çözücüler için simülasyonu son derece zordur.

from dynlib import setup
from dynlib.plot import series, export, fig, phase
from dynlib.utils import Timer

stepper = "tr-bdf2a"
mu = 1000.0

sim = setup("builtin://ode/vanderpol", 
            stepper=stepper, 
            jit=True,
            disk_cache=False)

sim.assign(mu=mu)
sim.config(dt=5e-4, max_steps=6_500_000)
with Timer("run simulation"):
    sim.run(T=3000.0)
res = sim.results()

series.plot(x=res.t, y=res["x"],
            title=f"Van der Pol Oscillator (μ={mu})",
            xlabel="Time",
            ylabel="x",
            ylim=(-3, 3),
            )

phase.xy(x=res["x"], y=res["y"])
export.show()

Çizim Yardımcıları Galerisi

Temel Çizim Fonksiyonları

Bu örnek, series.stem, series.step, utils.hist, phase.xy ve series.plot'un map ve mixed stilleri gibi daha az kullanılan çizim fonksiyonlarını altı farklı alt grafikte gösterir. Her bir fonksiyonun farklı veri türlerini (sürekli, ayrık, dağılım) nasıl işlediğini hızlıca görmek için bir referans niteliğindedir.

from __future__ import annotations
"""
Demonstration of various plotting functions from dynlib.plot module.
Only the ones not used in other examples are shown here.
"""

import numpy as np
from dynlib.plot import fig, series, phase, utils, export, theme


def main() -> None:
    theme.use("notebook")

    # ----- Time data -----
    t = np.linspace(0, 10, 101)
    y = np.sin(2 * np.pi * 0.5 * t)
    y2 = np.cos(2 * np.pi * 0.5 * t)
    # step-like signal
    y_step = np.floor(t) % 2
    # noisy distribution for histogram
    y_hist = y + 0.3 * np.random.randn(len(t))

    # ----- Discrete (map) data -----
    r = 3.7
    x0 = 0.2
    n_iter = 30
    xs = [x0]
    x = x0
    for k in range(n_iter):
        x = r * x * (1 - x)
        xs.append(x)
    ks = np.arange(len(xs))
    xs = np.array(xs)

    # Layout: 3 rows x 2 cols
    ax = fig.grid(rows=3, cols=2, size=(10, 12), sharex=False)

    # Row 0, Col 0: series.stem
    series.stem(
        x=t,
        y=y,
        ax=ax[0, 0],
        label="sin(t) stems",
        color="C0",
        xlabel="$t$",
        ylabel="$y$",
        title="series.stem: stem plot",
    )

    # Row 0, Col 1: series.step
    series.step(
        x=t,
        y=y_step,
        ax=ax[0, 1],
        label="step(t)",
        color="C1",
        xlabel="$t$",
        ylabel="$y$",
        title="series.step: step plot",
    )

    # Row 1, Col 0: utils.hist
    utils.hist(
        y=y_hist,
        bins=30,
        density=False,
        ax=ax[1, 0],
        color="C2",
        title="utils.hist: histogram",
        xlabel="$y$",
        ylabel="count",
    )

    # Row 1, Col 1: phase.xy with scatter style (for discrete maps)
    phase.xy(
        x=y,
        y=y2,
        style="scatter",
        ax=ax[1, 1],
        ms=6,
        color="C3",
        title="phase.xy: sin vs cos (scatter)",
        xlabel=r"$\sin$",
        ylabel=r"$\cos$",
    )

    # Row 2, Col 0: series with discrete/map style (stem-like effect)
    series.plot(
        x=ks,
        y=xs,
        style="map",
        ax=ax[2, 0],
        color="C4",
        title="series.plot: logistic iterations (map style)",
        xlabel="n",
        ylabel="$x_n$",
    )

    # Row 2, Col 1: series with mixed style (line + markers)
    series.plot(
        x=ks,
        y=xs,
        style="mixed",
        ax=ax[2, 1],
        color="C5",
        title="series.plot: logistic iterations (mixed style)",
        xlabel="n",
        ylabel="$x_n$",
    )

    # Tighten layout and show
    export.show()


if __name__ == "__main__":
    main()

Tema Ayarları

Aşağıdaki betik, notebook, paper, talk, dark ve mono gibi mevcut tüm tema ön ayarlarını dener. Her bir tema için örnek bir figür oluşturur ve PNG dosyası olarak kaydeder. Bu sayede her ön ayarın renkleri, ızgara çizgilerini ve tipografiyi nasıl etkilediğini inceleyebilirsiniz.

#!/usr/bin/env python3
"""
Demonstration of all theme presets available in dynlib.plot.theme.

This script creates a sample figure for each preset and saves it as an image.
"""

import numpy as np
from dynlib.plot import fig, series, theme, export, utils


def create_sample_figure():
    """Create a sample figure with various plot elements."""
    # Generate sample data
    t = np.linspace(0, 10, 100)
    y1 = np.sin(t)
    y2 = np.cos(t)
    y3 = np.sin(t) * np.exp(-t / 10)

    # Create figure with subplots
    ax = fig.grid(rows=2, cols=2, size=(8, 6))

    # Line plot
    series.plot(x=t, y=y1, ax=ax[0, 0], label="sin(t)", xlabel="Time", ylabel="Amplitude", title="Line Plot")

    # Scatter plot
    series.plot(x=t[::5], y=y2[::5], ax=ax[0, 1], style="scatter", label="cos(t) samples", xlabel="Time", ylabel="Amplitude", title="Scatter Plot")

    # Multiple lines
    series.plot(x=t, y=y1, ax=ax[1, 0], label="sin(t)", color="C0")
    series.plot(x=t, y=y2, ax=ax[1, 0], label="cos(t)", color="C1")
    series.plot(x=t, y=y3, ax=ax[1, 0], label="damped sin(t)", color="C2")
    ax[1, 0].set_xlabel("Time")
    ax[1, 0].set_ylabel("Amplitude")
    ax[1, 0].set_title("Multiple Lines")
    ax[1, 0].legend()

    # Histogram
    data = np.random.normal(0, 1, 1000)
    utils.hist(y=data, ax=ax[1, 1], bins=30, xlabel="Value", ylabel="Frequency", title="Histogram")

    return ax[0, 0].figure


def main():
    """Demonstrate each theme preset."""
    presets = ["notebook", "paper", "talk", "dark", "mono"]

    for preset in presets:
        print(f"Creating figure with '{preset}' preset...")

        # Apply theme
        theme.use(preset)

        # Create sample figure
        fig = create_sample_figure()

        # Save figure
        export.savefig(fig, f"theme_{preset}", fmts=("png",), dpi=150)

        print(f"Saved theme_{preset}.png")


if __name__ == "__main__":
    main()

Facet (Bölümlenmiş) Grafikler

plot.facet.wrap fonksiyonu, farklı kategorilerdeki veriler için bir alt grafik ızgarası oluşturmayı kolaylaştırır. Her bir eksen, kendi veri dilimini ve başlık/etiketleri otomatik olarak alır. Bu, veri dağılımlarını manuel olarak plt.subplots oluşturmadan keşfetmek için kullanışlıdır.

import dynlib.plot as plot
import numpy as np

# Sample data: a dictionary where keys are categories and values are data arrays
data = {
    'Category A': np.random.randn(100),
    'Category B': np.random.randn(100) + 1,
    'Category C': np.random.randn(100) - 1,
}

# Create facets: 2 columns, with a title
for ax, key in plot.facet.wrap(data.keys(), cols=2, title='Data by Category'):
    values = data[key]
    ax.hist(values, bins=20, alpha=0.7)
    ax.set_title(f'Histogram for {key}')
    ax.set_xlabel('Value')
    ax.set_ylabel('Frequency')

# Display the plot
plot.export.show()

Vektör Alanları

Temel Vektör Alanı Çizimi

Bu örnek, plot.vectorfield aracını kullanarak bir spiral modelin vektör alanını çizer. Örnekte, nullcline'ların (sıfır büyüme çizgileri) gösterimi, akış hızına göre renklendirme (speed_color) ve geri dönen handle nesnesi aracılığıyla parametrelerin (a, b) güncellenerek grafiğin yeniden çizilmesi gösterilmektedir.

from __future__ import annotations

"""
Demonstration of the dynlib.plot.vectorfield helper.
"""

import numpy as np

from dynlib import build, plot


def _make_model():
    # Simple spiral system with tunable linear terms
    model_uri = """
inline:
[model]
type = "ode"
name = "spiral"

[sim]
t0 = 0.0
dt = 0.1

[states]
x = 0.0
y = 0.0

[params]
a = 0.8
b = 0.2

[equations.rhs]
x = "a * x - y"
y = "x + b * y"
"""
    return build(model_uri, jit=False, disk_cache=False)


def main() -> None:
    plot.theme.use("notebook")
    model = _make_model()

    ax = plot.fig.single(title="Vector field demo")

    handle = plot.vectorfield(
        model,
        ax=ax,
        xlim=(-2, 2),
        ylim=(-2, 2),
        grid=(25, 25),
        normalize=True,
        speed_color=True,
        speed_cmap="plasma",
        nullclines=True,
        nullcline_style={"colors": ["#333333"], "linewidths": 1.2, "alpha": 0.6},
    )

    # Update parameters and redraw to illustrate handle.update()
    handle.update(params={"a": 1.2, "b": -0.1})

    plot.export.show()


if __name__ == "__main__":
    main()

Yüksek Boyutlu Sistemlerin Vektör Alanı Kesitleri

Bu örnekte, 3-boyutlu Lorenz sisteminin vektör alanı, seçilen 2-boyutlu düzlemlere (x/y ve y/z) yansıtılır. fixed parametresi ile sabitlenen durum değişkenlerinin (z ve x) değerleri ayarlanabilir. Ayrıca, interactive=True seçeneği aktifleştirildiği için panellerden herhangi birine tıklayarak o kesit üzerinden geçen kısa bir yörüngeyi anında çizebilirsiniz.

from __future__ import annotations

"""
Demonstration of projecting a higher-dimensional vector field onto chosen 2D planes.

We use the 3D Lorenz system and visualize two slices:
- x/y plane with z fixed (and then updated to a new z to show handle.update)
- y/z plane with x fixed

Click on either panel to launch a short trajectory through that slice.
"""

from dynlib import build, plot


def _lorenz_model():
    model_uri = """
inline:
[model]
type = "ode"
name = "lorenz-3d"
stepper = "rk4"

[sim]
t0 = 0.0
dt = 0.01
t_end = 8.0

[states]
x = 1.0
y = 1.0
z = 1.0

[params]
sigma = 10.0
rho = 28.0
beta = 2.6666666666666665

[equations.rhs]
x = "sigma * (y - x)"
y = "x * (rho - z) - y"
z = "x * y - beta * z"
"""
    return build(model_uri, jit=False, disk_cache=False)


def main() -> None:
    plot.theme.use("notebook")
    model = _lorenz_model()

    ax = plot.fig.grid(rows=1, cols=2, size=(12, 5), sharex=False, sharey=False)

    handle_xy = plot.vectorfield(
        model,
        ax=ax[0, 0],
        vars=("x", "y"),
        fixed={"z": 5.0},
        xlim=(-20, 20),
        ylim=(-30, 30),
        grid=(25, 25),
        normalize=False,
        nullclines=False,
        T=6.0,
        dt=0.01,
        trajectory_style={"color": "C0"},
    )
    ax[0, 0].set_title("x-y slice with z fixed (click to trace)", fontsize=11)

    handle_yz = plot.vectorfield(
        model,
        ax=ax[0, 1],
        vars=("y", "z"),
        fixed={"x": 0.0},
        xlim=(-30, 30),
        ylim=(0, 50),
        grid=(25, 25),
        normalize=False,
        nullclines=False,
        T=6.0,
        dt=0.01,
        trajectory_style={"color": "C1"},
    )
    ax[0, 1].set_title("y-z slice with x fixed (click to trace)", fontsize=11)

    # Show that fixed values can be updated without rebuilding the figure.
    handle_xy.update(fixed={"z": 15.0}, redraw=True)
    handle_xy.ax.set_title("x-y slice with z updated to 15.0", fontsize=11)

    plot.export.show()


if __name__ == "__main__":
    main()

Parametre Değerlerine Göre Vektör Alanı Taraması

plot.vectorfield_sweep fonksiyonu, bir model parametresinin (a) farklı değerleri için vektör alanlarını tek bir ızgara üzerinde otomatik olarak çizer. Bu, farklı parametre rejimlerinin sistemin dinamiğini nasıl değiştirdiğini bir bakışta karşılaştırmanızı sağlar.

from __future__ import annotations

"""Demonstration of plot.vectorfield_sweep for a simple 2D system."""

from dynlib import build, plot


def _make_model():
    model_uri = """
inline:
[model]
type = "ode"
name = "spiral"

[sim]
t0 = 0.0
dt = 0.05

[states]
x = 0.0
y = 0.0

[params]
a = 0.5
b = -0.2

[equations.rhs]
x = "a * x - y"
y = "x + b * y"
"""
    return build(model_uri, jit=False, disk_cache=False)


def main() -> None:
    plot.theme.use("notebook")
    model = _make_model()

    plot.vectorfield_sweep(
        model,
        param="a",
        values=[-0.6, 0.0, 0.6, 1.2],
        xlim=(-2.5, 2.5),
        ylim=(-2.5, 2.5),
        grid=(22, 22),
        normalize=True,
        speed_color=True,
        speed_cmap="plasma",
        cols=2,
        facet_titles="a={value:.2f}",
        title="Vector field sweep over parameter 'a'",
        nullclines=True,
        nullcline_style={"colors": ["#333333"], "linewidths": 1.0, "alpha": 0.6},
        interactive=False,
        size=(8,6)
    )

    plot.export.show()


if __name__ == "__main__":
    main()

Animasyonlar

Vektör Alanı Animasyonları

Aşağıdaki örnek, plot.vectorfield_animate kullanarak bir parametrenin (a) belirli değerler arasında nasıl değiştirileceğini ve bu değişimin vektör alanını nasıl etkilediğini gösteren bir animasyon oluşturur. Oluşturulan anim nesnesi, bir değişkene atanmalıdır; aksi takdirde Python'un çöp toplayıcısı (garbage collector) tarafından silinebilir ve animasyon görüntülenmez.

from __future__ import annotations

"""Demonstration of plot.vectorfield_animate for a simple spiral system."""

from dynlib import build, plot


def _make_model():
    model_uri = """
inline:
[model]
type = "ode"
name = "spiral"

[sim]
t0 = 0.0
dt = 0.05

[states]
x = 0.0
y = 0.0

[params]
a = -0.4
b = 0.25

[equations.rhs]
x = "a * x - y"
y = "x + b * y"
"""
    return build(model_uri, jit=False, disk_cache=False)


def main() -> None:
    plot.theme.use("notebook")
    model = _make_model()

    values = [v for v in (-1.0, -0.4, 0.0, 0.6, 1.0, 1.4)]
    # You have to define anim (or any other name) even if you don't use it. 
    # Otherwise it gets garbage collected.
    anim = plot.vectorfield_animate(
        model,
        param="a",
        values=values,
        xlim=(-2.5, 2.5),
        ylim=(-2.5, 2.5),
        grid=(24, 24),
        normalize=True,
        speed_color=True,
        speed_cmap="plasma",
        title_func=lambda v, idx: f"Vector field: a={float(v):.2f}",
        nullclines=True,
        nullcline_style={"colors": ["#333333"], "linewidths": 1.0, "alpha": 0.6},
        interactive=False,
        fps=3,
        blit=False,
    )

    # Preview the animation in notebook/backends that support it, or save via anim.animation.save(...)
    plot.export.show()


if __name__ == "__main__":
    main()

Bu örnekte ise, bir sin/cos tabanlı vektör alanının frekans parametresi k, 300 kare boyunca taranarak bir animasyon oluşturulur.

# An interesting animation that I like.

import numpy as np
from dynlib.plot import export, vectorfield_animate


DSL = """
inline:
[model]
type="ode"

[states]
x=0.0
y=0.0

[params]
k=0.0

[equations.rhs]
x = "sin(k*y)"
y = "sin(k*x)"
"""

# You have to define anim (or any other name) even if you don't use it. 
# Otherwise it gets garbage collected.
anim=vectorfield_animate(DSL, 
                    param="k", 
                    values=np.linspace(0.1,10,300), 
                    xlim=(-10, 10), 
                    ylim=(-10, 10), 
                    grid=(24, 24), 
                    interval=130,
                    normalize=True,
                    title_func=lambda v, idx: f"Vector field: k={float(v):.2f}",
                    )

# Save using writer of your choice, e.g., "ffmpeg", "pillow", etc.
# anim.save("vectorfield_animation.gif", writer="pillow", dpi=150)

export.show()