Skip to content

Commit

Permalink
Migrate to uv
Browse files Browse the repository at this point in the history
  • Loading branch information
CarloLepelaars committed Dec 3, 2024
1 parent fd8d2ed commit 260f139
Show file tree
Hide file tree
Showing 11 changed files with 965 additions and 1,159 deletions.
34 changes: 23 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Poetry CI
name: CI

on:
push:
Expand All @@ -12,27 +12,39 @@ jobs:
strategy:
matrix:
python-version: ['3.10', '3.11', '3.12']

steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}

- name: Install Poetry
- name: Install uv
run: |
curl -sSL https://install.python-poetry.org | python3 -
curl -LsSf https://astral.sh/uv/install.sh | sh
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Configure Poetry
- name: Create and activate virtual environment
run: |
echo "$HOME/.local/bin" >> $GITHUB_PATH
echo "$(python -m site --user-base)/bin" >> $GITHUB_PATH
poetry config virtualenvs.create false --local
uv venv
echo "${{ github.workspace }}/.venv/bin" >> $GITHUB_PATH
- name: Install dependencies
run: poetry install
run: |
uv pip install 'setuptools[pkg_resources]'
uv pip install -e ".[dev]"
- name: Run tests
run: |
pytest -s
- name: Build wheel
run: |
uv pip install build
python -m build --wheel
- name: Install built wheel
run: |
uv pip install dist/*.whl
8 changes: 8 additions & 0 deletions .github/workflows/ruff.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
name: Ruff
on: [push, pull_request]
jobs:
ruff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/[email protected]
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# fh-plotly

![Downloads](https://img.shields.io/pypi/dm/fh-plotly)
![Python Version](https://img.shields.io/badge/dynamic/toml?url=https://raw.githubusercontent.com/carlolepelaars/fh-plotly/main/pyproject.toml&query=%24.project%5B%22requires-python%22%5D&label=python&color=blue)
[![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)

Use Plotly charts with [FastHTML](https://github.com/AnswerDotAI/fasthtml).


Expand Down Expand Up @@ -42,10 +47,11 @@ def generate_line_chart():
## Contributing

Feel free to open an issue or a pull request.
Make sure to install with `poetry install` for an editable install with dev dependencies when working on contributions.
Make sure to install with `uv install` for an editable install with dev dependencies when working on contributions.

```bash
poetry install
pip install uv
uv pip install -e ".[dev]"
```

To run tests:
Expand Down
156 changes: 58 additions & 98 deletions examples/bloch.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

app, rt = fast_app(hdrs=plotly_headers)


def plot_bloch(state: np.array):
fig = go.Figure()

Expand All @@ -19,163 +20,122 @@ def plot_bloch(state: np.array):
z = np.cos(theta)

# Surface coordinates
surface_phi, surface_theta = np.mgrid[0:2*np.pi:100j, 0:np.pi:50j]
surface_phi, surface_theta = np.mgrid[0 : 2 * np.pi : 100j, 0 : np.pi : 50j]
xs = np.sin(surface_theta) * np.cos(surface_phi)
ys = np.sin(surface_theta) * np.sin(surface_phi)
zs = np.cos(surface_theta)
fig.add_trace(go.Surface(x=xs, y=ys, z=zs, opacity=0.5, colorscale='Blues', showscale=False))

fig.add_trace(go.Scatter3d(
x=[0, x],
y=[0, y],
z=[0, z],
mode='lines+markers+text',
marker=dict(size=10, color='green'),
line=dict(color='green', width=8),
textposition="top center",
showlegend=True,
name=f"{alpha:.2f}|0⟩ + {beta:.2f}|1⟩"
))
fig.add_trace(go.Surface(x=xs, y=ys, z=zs, opacity=0.5, colorscale="Blues", showscale=False))

fig.add_trace(go.Scatter3d(x=[0, x], y=[0, y], z=[0, z], mode="lines+markers+text", marker=dict(size=10, color="green"), line=dict(color="green", width=8), textposition="top center", showlegend=True, name=f"{alpha:.2f}|0⟩ + {beta:.2f}|1⟩"))

# Mark basis states
fig.add_trace(go.Scatter3d(
x=[0, 0, 1, -1, 0, 0],
y=[0, 0, 0, 0, 1, -1],
z=[1, -1, 0, 0, 0, 0],
mode='markers',
marker=dict(size=5, color='black'),
hovertext=['|0⟩', '|1⟩', '|+⟩', '|-⟩', '|i⟩', '|-i⟩'],
showlegend=False,
name="Basis states"
))
fig.add_trace(go.Scatter3d(x=[0, 0, 1, -1, 0, 0], y=[0, 0, 0, 0, 1, -1], z=[1, -1, 0, 0, 0, 0], mode="markers", marker=dict(size=5, color="black"), hovertext=["|0⟩", "|1⟩", "|+⟩", "|-⟩", "|i⟩", "|-i⟩"], showlegend=False, name="Basis states"))

# Add lines across axes
boundary_phi = np.linspace(0, 2 * np.pi, 100)
coords = [
(np.cos(boundary_phi), np.sin(boundary_phi), np.zeros_like(boundary_phi)),
(np.zeros_like(boundary_phi), np.cos(boundary_phi), np.sin(boundary_phi)),
(np.cos(boundary_phi), np.zeros_like(boundary_phi), np.sin(boundary_phi))
]
coords = [(np.cos(boundary_phi), np.sin(boundary_phi), np.zeros_like(boundary_phi)), (np.zeros_like(boundary_phi), np.cos(boundary_phi), np.sin(boundary_phi)), (np.cos(boundary_phi), np.zeros_like(boundary_phi), np.sin(boundary_phi))]
for x, y, z in coords:
fig.add_trace(go.Scatter3d(
x=x,
y=y,
z=z,
mode='lines',
line=dict(color='black', width=2),
showlegend=False,
name="Axes"
))
fig.add_trace(go.Scatter3d(x=x, y=y, z=z, mode="lines", line=dict(color="black", width=2), showlegend=False, name="Axes"))

fig.update_layout(
scene=dict(
xaxis=dict(title=dict(text='X', font=dict(size=25)),
range=[-1.2, 1.2], tickvals=[-1, 1],
ticktext=["|-⟩", "|+⟩"],
tickfont=dict(size=15)),
yaxis=dict(title=dict(text='Y', font=dict(size=25)),
range=[-1.2, 1.2], tickvals=[-1, 1],
ticktext=["|-i⟩", "|i⟩"],
tickfont=dict(size=15)),
zaxis=dict(title=dict(text='Z', font=dict(size=25)),
range=[-1.2, 1.2], tickvals=[-1, 1],
ticktext=["|1⟩", "|0⟩"],
tickfont=dict(size=15)),
aspectmode='cube',
xaxis=dict(title=dict(text="X", font=dict(size=25)), range=[-1.2, 1.2], tickvals=[-1, 1], ticktext=["|-⟩", "|+⟩"], tickfont=dict(size=15)),
yaxis=dict(title=dict(text="Y", font=dict(size=25)), range=[-1.2, 1.2], tickvals=[-1, 1], ticktext=["|-i⟩", "|i⟩"], tickfont=dict(size=15)),
zaxis=dict(title=dict(text="Z", font=dict(size=25)), range=[-1.2, 1.2], tickvals=[-1, 1], ticktext=["|1⟩", "|0⟩"], tickfont=dict(size=15)),
aspectmode="cube",
),
legend=dict(
font=dict(size=20),
x=0.05,
y=0.95,
xanchor='left',
yanchor='top',
bgcolor='rgba(0,0,0,0)',
),
margin=dict(l=0, r=0, t=0, b=0)
font=dict(size=20),
x=0.05,
y=0.95,
xanchor="left",
yanchor="top",
bgcolor="rgba(0,0,0,0)",
),
margin=dict(l=0, r=0, t=0, b=0),
)
return plotly2fasthtml(fig)


def construct_state(gates: list[str]):
state = np.array([1, 0]) # |0> basis state
state = np.array([1, 0]) # |0> basis state
for gate in gates:
state = single_qubit_gates[gate] @ state
return state


def visualize_circuit(gates: list[str]):
circuit = "|0⟩-"
circuit = "|0⟩-"
for gate in gates:
circuit += f"[{gate}]─"
return circuit + "|"
return circuit + "|"


single_qubit_gates = {
# Hadamard
"H": np.array([[1, 1],
[1, -1]]) / np.sqrt(2),
"H": np.array([[1, 1], [1, -1]]) / np.sqrt(2),
# Pauli matrices
"X": np.array([[0, 1],
[1, 0]]),
"Y": np.array([[0, -1j],
[1j, 0]]),
"Z": np.array([[1, 0],
[0, -1]]),
"X": np.array([[0, 1], [1, 0]]),
"Y": np.array([[0, -1j], [1j, 0]]),
"Z": np.array([[1, 0], [0, -1]]),
# Phase gates
"S": np.array([[1, 0],
[0, 1j]]),
"T": np.array([[1, 0],
[0, np.exp(1j * np.pi / 4)]])
"S": np.array([[1, 0], [0, 1j]]),
"T": np.array([[1, 0], [0, np.exp(1j * np.pi / 4)]]),
}


def apply_gate(gate: str = None):
global gates
if gate:
gates.append(gate)
state = construct_state(gates)
return Div(
plot_bloch(state),
H4(f"Quantum Circuit: {visualize_circuit(gates)}"),
id="chart"
)
return Div(plot_bloch(state), H4(f"Quantum Circuit: {visualize_circuit(gates)}"), id="chart")


# Construct gate routing and buttons for each qubit gate
gates = []
for gate in single_qubit_gates.keys():

def create_apply_func(gate_name):
def apply_func():
return apply_gate(gate_name)

return apply_func

func = create_apply_func(gate)
route_path = f'/{gate.lower()}'
route_path = f"/{gate.lower()}"
app.get(route_path)(func)

@app.get('/reset')

@app.get("/reset")
def reset():
global gates
gates = []
return apply_gate()

buttons = [
Button(gate, hx_get=f"/{gate.lower()}", hx_target="#chart", hx_swap="innerHTML",
title=f"Apply {gate} gate")
for gate in single_qubit_gates.keys()
] + [Button("Reset", hx_get="/reset", hx_target="#chart", hx_swap="innerHTML",
title="Reset the circuit")]

buttons = [Button(gate, hx_get=f"/{gate.lower()}", hx_target="#chart", hx_swap="innerHTML", title=f"Apply {gate} gate") for gate in single_qubit_gates.keys()] + [Button("Reset", hx_get="/reset", hx_target="#chart", hx_swap="innerHTML", title="Reset the circuit")]

desc = """
The Bloch Sphere is a 3D visualization of a single quantum state.
You can interact with the buttons (Gates) to see how the state changes. See the description below for more information on what each gate represents.
"""

@app.get('/')

@app.get("/")
def homepage():
return Title("Interactive Bloch Sphere"), Main(P(desc),
*buttons,
Div(apply_gate(), id="chart"),
H4("Available gates"),
P("- H: Hadamard gate. Puts the state in superposition. "),
P("- X: Pauli-X (NOT) gate. Rotate 180 degrees around the X-Axis."),
P("- Y: Pauli-Y (\"bit-flip\") gate. Rotate 180 degrees around the Y-Axis."),
P("- Z: Pauli-Z (\"phase-flip\") gate. Rotate 180 degrees around the Z-Axis."),
P("- S: Phase gate. Rotates around the Z-axis by 90 degrees."),
P("- T: π/8 gate. Rotates around the Z-axis by 45 degrees."))
return Title("Interactive Bloch Sphere"), Main(
P(desc),
*buttons,
Div(apply_gate(), id="chart"),
H4("Available gates"),
P("- H: Hadamard gate. Puts the state in superposition. "),
P("- X: Pauli-X (NOT) gate. Rotate 180 degrees around the X-Axis."),
P('- Y: Pauli-Y ("bit-flip") gate. Rotate 180 degrees around the Y-Axis.'),
P('- Z: Pauli-Z ("phase-flip") gate. Rotate 180 degrees around the Z-Axis.'),
P("- S: Phase gate. Rotates around the Z-axis by 90 degrees."),
P("- T: π/8 gate. Rotates around the Z-axis by 45 degrees."),
)


serve()
43 changes: 17 additions & 26 deletions examples/test_app.py
Original file line number Diff line number Diff line change
@@ -1,49 +1,40 @@
from fasthtml.common import fast_app, serve, Div, H1, P, Script
from fasthtml.common import fast_app, serve, Div, H1, P
import pandas as pd
import numpy as np
import plotly.express as px

from fh_plotly import plotly2fasthtml, plotly_headers


app, rt = fast_app(hdrs=plotly_headers)


def generate_line_chart():
df = pd.DataFrame({'y': [1, 2, 3, 2], 'x': [3, 1, 2, 4]})
fig = px.line(df, x='x', y='y')
df = pd.DataFrame({"y": [1, 2, 3, 2], "x": [3, 1, 2, 4]})
fig = px.line(df, x="x", y="y")
return plotly2fasthtml(fig)


def generate_bar_chart():
df = pd.DataFrame({'y': [1, 2, 3], 'x': ['A', 'B', 'C']})
fig = px.bar(df, x='x', y='y')
df = pd.DataFrame({"y": [1, 2, 3], "x": ["A", "B", "C"]})
fig = px.bar(df, x="x", y="y")
return plotly2fasthtml(fig)


def generate_scatter_chart():
df = pd.DataFrame({'y': [1, 2, 3, 2], 'x': [3, 1, 2, 4], 'size': [10, 20, 30, 40]})
fig = px.scatter(df, x='x', y='y', size='size')
df = pd.DataFrame({"y": [1, 2, 3, 2], "x": [3, 1, 2, 4], "size": [10, 20, 30, 40]})
fig = px.scatter(df, x="x", y="y", size="size")
return plotly2fasthtml(fig)


def generate_3d_scatter_chart():
df = pd.DataFrame({
'x': [1, 2, 3, 4],
'y': [10, 11, 12, 13],
'z': [100, 110, 120, 130]
})
fig = px.scatter_3d(df, x='x', y='y', z='z')
df = pd.DataFrame({"x": [1, 2, 3, 4], "y": [10, 11, 12, 13], "z": [100, 110, 120, 130]})
fig = px.scatter_3d(df, x="x", y="y", z="z")
return plotly2fasthtml(fig)

@app.get('/')

@app.get("/")
def home():
return Div(
H1("Plotly Charts Demo with FastHTML"),
P("Plot 1: Line Chart"),
Div(generate_line_chart()),
P("Plot 2: Bar Chart"),
Div(generate_bar_chart()),
P("Plot 3: Scatter Chart"),
Div(generate_scatter_chart()),
P("Plot 4: 3D Scatter Chart"),
Div(generate_3d_scatter_chart())
)
return Div(H1("Plotly Charts Demo with FastHTML"), P("Plot 1: Line Chart"), Div(generate_line_chart()), P("Plot 2: Bar Chart"), Div(generate_bar_chart()), P("Plot 3: Scatter Chart"), Div(generate_scatter_chart()), P("Plot 4: 3D Scatter Chart"), Div(generate_3d_scatter_chart()))


serve()
1 change: 0 additions & 1 deletion fh_plotly/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
from fh_plotly.p2fh import plotly2fasthtml, plotly_headers

Loading

0 comments on commit 260f139

Please sign in to comment.