-
Notifications
You must be signed in to change notification settings - Fork 6
Add helper to plot counts from results #452
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
35 commits
Select commit
Hold shift + click to select a range
375f98f
Add helper to plot counts from results
alessandro-santini e242233
precommit
alessandro-santini 2575beb
added option to stack different runs
alessandro-santini 4e2d6dd
Simplified visualization.py
alessandro-santini 8a5f5ea
update notebooks
alessandro-santini a075254
added flag for xlabel and ylabel
alessandro-santini 43eb7db
Change color naming to colors
alessandro-santini 628cd32
Revert colors in color, added some checks on the lists lengths
alessandro-santini 778f92c
precommit
alessandro-santini 4f06f89
More forgiving with legend labels format
alessandro-santini d95a356
Added test
alessandro-santini 5d6bef7
precommit
alessandro-santini 0a9e976
Gone through changes
alessandro-santini 92acd16
precommit
alessandro-santini 432b476
fixed docstring
alessandro-santini a98d245
Added fallback label for legend
alessandro-santini 764064b
Add fallback label only when there is more than one histogram
alessandro-santini 09bf443
Rename default legend labels
alessandro-santini 8723140
Apply suggestions from code review
alessandro-santini 9691d64
Fix labels position
alessandro-santini e2cb091
fix docstring
alessandro-santini 936a653
Changed highlight behavior
alessandro-santini 08d16bd
Fix comment
alessandro-santini f3b714d
uniform colors
alessandro-santini a61c571
Update visualization.py
alessandro-santini c829d48
Update visualization.py
alessandro-santini 90fc216
Update visualization.py
alessandro-santini a67993d
Merge branch 'main' into as/451-add-plot-histogram-function
sgrava 7809752
format
sgrava 726e6b5
Merge branch 'main' into as/451-add-plot-histogram-function
sgrava 2e9474a
simplify API
alessandro-santini 47fc4e9
Kept the old way of highlighting the Histogram since now we just plot…
alessandro-santini e64fa65
changed comment
alessandro-santini a7ebfe2
small fix
alessandro-santini 811c80f
more small fix
alessandro-santini File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| """Visualization helpers for QoolQit results.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from collections import Counter | ||
|
|
||
| import matplotlib.pyplot as plt | ||
| from matplotlib.axes import Axes | ||
|
|
||
| DEFAULT_BAR_COLOR = "#397378" | ||
|
|
||
|
|
||
| def plot_bitstrings( | ||
| counts: dict[str, int], | ||
| top: int | None = None, | ||
| normalize: bool = False, | ||
| color: str = DEFAULT_BAR_COLOR, | ||
| highlight: dict[str, str] | None = None, | ||
| label: str | None = None, | ||
| ax: Axes | None = None, | ||
| ) -> None: | ||
| """Plot bitstring counts, optionally highlighting selected ones. | ||
|
|
||
| Arguments: | ||
| counts: Mapping of bitstrings to counts. | ||
| top: Plot only the top N counts. | ||
| normalize: Normalize counts to probabilities. Defaults to False. | ||
| color: Bar color. | ||
| highlight: Mapping of bitstrings to highlight colors. Highlighted | ||
| outcomes get their bar and tick label colored accordingly. | ||
| label: Legend label for the bars. Call ax.legend() to show it. | ||
| ax: Axes to draw on. Creates new axes if omitted. | ||
| """ | ||
|
|
||
| if not counts: | ||
| raise ValueError("counts cannot be empty") | ||
|
|
||
| total = sum(counts.values()) | ||
| if normalize and total == 0: | ||
| raise ValueError("cannot plot normalized counts with zero total counts") | ||
|
|
||
| if top is not None and top <= 0: | ||
| raise ValueError("top must be a positive integer") | ||
|
|
||
| # most_common(None) returns all entries, sorted by decreasing count | ||
| selected_counts = Counter(counts).most_common(top) | ||
| bitstrings = [bitstring for bitstring, _ in selected_counts] | ||
| values = [count / total if normalize else count for _, count in selected_counts] | ||
|
|
||
| highlight = highlight or {} | ||
|
|
||
| # Create the plot if no axes are provided | ||
| if ax is None: | ||
| _, ax = plt.subplots(figsize=(12, 5)) | ||
|
|
||
| positions = range(len(bitstrings)) | ||
| bar_colors = [highlight.get(bitstring, color) for bitstring in bitstrings] | ||
| ax.bar(positions, values, width=0.65, color=bar_colors) | ||
|
|
||
| if label is not None: | ||
| # A zero-height bar draws nothing but gives the legend a swatch in | ||
| # `color`, regardless of which bitstrings are highlighted. | ||
| ax.bar(0, 0, color=color, label=label) | ||
|
|
||
| ax.set_xticks(list(positions)) | ||
| ax.set_xticklabels(bitstrings) | ||
|
|
||
| # Highlighted outcomes are also marked on their tick label | ||
| for tick_label in ax.get_xticklabels(): | ||
| if tick_label.get_text() in highlight: | ||
| tick_label.set_color(highlight[tick_label.get_text()]) | ||
| tick_label.set_fontweight("bold") | ||
|
|
||
| ax.tick_params(axis="x", labelrotation=90) | ||
| ax.grid(axis="y", linestyle="--", alpha=0.4) | ||
|
|
||
| # Default labels; the caller can override via ax.set_xlabel/ax.set_ylabel | ||
| ax.set_ylabel("Probability" if normalize else "Counts") | ||
| ax.set_xlabel("Bitstrings") | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| from __future__ import annotations | ||
|
|
||
| import matplotlib.pyplot as plt | ||
| import pytest | ||
| from matplotlib.colors import to_rgba | ||
|
|
||
| from qoolqit.visualization import DEFAULT_BAR_COLOR, plot_bitstrings | ||
|
|
||
|
|
||
| def test_plot_bitstrings_errors() -> None: | ||
| with pytest.raises(ValueError, match="counts cannot be empty"): | ||
| plot_bitstrings(counts={}) | ||
| with pytest.raises(ValueError, match="cannot plot normalized counts with zero total counts"): | ||
| plot_bitstrings(counts={"000": 0}, normalize=True) | ||
| with pytest.raises(ValueError, match="top must be a positive integer"): | ||
| plot_bitstrings(counts={"000": 1, "001": 2}, top=0) | ||
|
|
||
|
|
||
| def test_plot_bitstrings_default_bar_color() -> None: | ||
| _, ax = plt.subplots() | ||
| plot_bitstrings(counts={"000": 1, "001": 2}, ax=ax) | ||
|
|
||
| bars = ax.containers[0] | ||
| assert all(bar.get_facecolor() == to_rgba(DEFAULT_BAR_COLOR) for bar in bars) | ||
|
|
||
|
|
||
| def test_plot_bitstrings_label_shows_up_in_legend() -> None: | ||
| _, ax = plt.subplots() | ||
| plot_bitstrings(counts={"000": 1, "001": 2}, label="run 1", ax=ax) | ||
|
|
||
| assert ax.get_legend() is None | ||
| legend = ax.legend() | ||
| assert legend.legend_handles[0].get_facecolor() == to_rgba(DEFAULT_BAR_COLOR) | ||
| assert legend.get_texts()[0].get_text() == "run 1" | ||
|
|
||
|
|
||
| def test_plot_bitstrings_highlight_colors_the_bar() -> None: | ||
| _, ax = plt.subplots() | ||
| plot_bitstrings(counts={"000": 1, "001": 2}, highlight={"001": "tab:red"}, ax=ax) | ||
|
|
||
| bars = dict(zip(["001", "000"], ax.containers[0])) | ||
| assert bars["001"].get_facecolor() == to_rgba("tab:red") | ||
| assert bars["000"].get_facecolor() == to_rgba(DEFAULT_BAR_COLOR) | ||
|
|
||
|
|
||
| def test_plot_bitstrings_legend_uses_base_color_even_if_first_bar_highlighted() -> None: | ||
| _, ax = plt.subplots() | ||
| # "001" has the higher count so it plots first, and is also highlighted. | ||
| plot_bitstrings(counts={"000": 1, "001": 2}, highlight={"001": "tab:red"}, label="run 1", ax=ax) | ||
|
|
||
| legend = ax.legend() | ||
| assert legend.legend_handles[0].get_facecolor() == to_rgba(DEFAULT_BAR_COLOR) | ||
|
|
||
|
|
||
| def test_plot_bitstrings_two_calls_on_same_axes_keep_their_own_highlights() -> None: | ||
| _, ax = plt.subplots() | ||
| plot_bitstrings(counts={"000": 1, "001": 2}, highlight={"001": "tab:red"}, ax=ax) | ||
| plot_bitstrings(counts={"000": 1, "001": 2}, color="C2", ax=ax) | ||
|
|
||
| first_call_bars = dict(zip(["001", "000"], ax.containers[0])) | ||
| assert first_call_bars["001"].get_facecolor() == to_rgba("tab:red") |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.