Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 21 additions & 11 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,31 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
pip install -e ".[dev]"
```

### Run Tests
```bash
# All tests
python -m pytest test_autoarray/
### Run Tests
```bash
# All tests
python -m pytest test_autoarray/

# Single test file
python -m pytest test_autoarray/structures/test_arrays.py

# With output
python -m pytest test_autoarray/structures/test_arrays.py -s
```

### Formatting
```bash
black autoarray/
# With output
python -m pytest test_autoarray/structures/test_arrays.py -s
```

### Codex / sandboxed runs

When running Python from Codex or any restricted environment, set writable cache directories so `numba` and `matplotlib` do not fail on unwritable home or source-tree paths:

```bash
NUMBA_CACHE_DIR=/tmp/numba_cache MPLCONFIGDIR=/tmp/matplotlib python -m pytest test_autoarray/
```

This workspace is often imported from `/mnt/c/...` and Codex may not be able to write to module `__pycache__` directories or `/home/jammy/.cache`, which can cause import-time `numba` caching failures without this override.

### Formatting
```bash
black autoarray/
```

## Architecture
Expand Down
12 changes: 7 additions & 5 deletions autoarray/dataset/plot/interferometer_plots.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,31 +42,33 @@ def subplot_interferometer_dataset(
fig, axes = plt.subplots(2, 3, figsize=conf_subplot_figsize(2, 3))
axes = axes.flatten()

plot_grid(dataset.data.in_grid, ax=axes[0], title="Visibilities")
plot_grid(dataset.data.in_grid, ax=axes[0], title="Visibilities", xlabel="", ylabel="")
plot_grid(
Grid2DIrregular.from_yx_1d(
y=dataset.uv_wavelengths[:, 1] / 10**3.0,
x=dataset.uv_wavelengths[:, 0] / 10**3.0,
),
ax=axes[1],
title="UV-Wavelengths",
xlabel="",
ylabel="",
)
plot_yx(
dataset.amplitudes,
dataset.uv_distances / 10**3.0,
ax=axes[2],
title="Amplitudes vs UV-distances",
ylabel="Jy",
xlabel="k$\\lambda$",
xtick_suffix='"',
ytick_suffix="Jy",
plot_axis_type="scatter",
)
plot_yx(
dataset.phases,
dataset.uv_distances / 10**3.0,
ax=axes[3],
title="Phases vs UV-distances",
ylabel="deg",
xlabel="k$\\lambda$",
xtick_suffix='"',
ytick_suffix="deg",
plot_axis_type="scatter",
)
plot_array(
Expand Down
61 changes: 55 additions & 6 deletions autoarray/plot/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -533,15 +533,52 @@ def _colorbar_tick_values(norm) -> Optional[List[float]]:
return [lo, mid, hi]


_SUPERSCRIPT_DIGITS = str.maketrans("0123456789-", "⁰¹²³⁴⁵⁶⁷⁸⁹⁻")


def _to_scientific(v: float) -> Optional[str]:
"""Convert *v* to Unicode scientific notation (e.g. ``4.3×10⁴``).

Returns ``None`` when ``f"{v:.2g}"`` does not produce an exponent (unusual
edge case for certain values near the g-format threshold).
"""
s = f"{v:.2g}"
if "e" not in s:
return None
mantissa, exp = s.split("e")
sign = "-" if exp.startswith("-") else ""
exp_num = exp.lstrip("+-").lstrip("0") or "0"
superscript = f"{sign}{exp_num}".translate(_SUPERSCRIPT_DIGITS)
return f"{mantissa}×10{superscript}"


def _fmt_tick(v: float) -> str:
"""Format a single tick value to 2 decimal places without scientific notation."""
"""Format a single tick value compactly.

Values with 5 or more digits (abs(v) >= 10000) or very small values
(abs(v) < 0.001) are rendered as compact scientific notation using
Unicode superscripts, e.g. ``4.3×10⁴`` or ``1.2×10⁻⁵``. This avoids
LaTeX expansion that would overflow the colorbar width. Values in
between are rendered with ``:.2f``.
"""
abs_v = abs(v)
if abs_v != 0 and (abs_v >= 10000 or abs_v < 0.001):
sci = _to_scientific(v)
return sci if sci is not None else f"{v:.2g}"
return f"{v:.2f}"


def _colorbar_tick_labels(tick_values: List[float], cb_unit: Optional[str] = None) -> List[str]:
"""Format tick values without scientific notation, appending *cb_unit* to the middle label.
"""Format tick values, appending *cb_unit* to the middle label.

All three labels use a consistent notation style: if any tick is rendered
in scientific notation (``×10ⁿ``), every non-zero tick is forced through
the same format. This prevents the central tick from showing e.g.
``-5000.00`` when the outer ticks show ``-2×10⁴`` / ``1.5×10⁴`` because
the midpoint happens to fall below the per-value threshold.

If *cb_unit* is ``None`` the unit is read from config; pass ``""`` for unitless panels.
If *cb_unit* is ``None`` the unit is read from config; pass ``""`` for
unitless panels.
"""
if cb_unit is None:
try:
Expand All @@ -551,6 +588,18 @@ def _colorbar_tick_labels(tick_values: List[float], cb_unit: Optional[str] = Non
cb_unit = ""
labels = [_fmt_tick(v) for v in tick_values]
mid = len(labels) // 2

# Enforce consistent notation: if any label uses ×10, convert all others.
if any("×10" in lbl for lbl in labels):
for i, (lbl, v) in enumerate(zip(labels, tick_values)):
if "×10" not in lbl:
if v == 0:
labels[i] = "0"
else:
sci = _to_scientific(v)
if sci is not None:
labels[i] = sci

labels[mid] = f"{labels[mid]}{cb_unit}"
return labels

Expand All @@ -569,8 +618,8 @@ def _apply_colorbar(
Override the unit string on the middle tick. Pass ``""`` for unitless panels.
``None`` reads the unit from config.
is_subplot
When ``True`` uses ``labelsize_subplot`` from config (default 22) instead of
the single-figure ``labelsize`` (default 22).
When ``True`` uses ``labelsize_subplot`` from config (default 18) instead of
the single-figure ``labelsize`` (default 18).
"""
tick_values = _colorbar_tick_values(getattr(mappable, "norm", None))

Expand All @@ -582,7 +631,7 @@ def _apply_colorbar(
ticks=tick_values,
)
labelsize_key = "labelsize_subplot" if is_subplot else "labelsize"
labelsize = float(_conf_colorbar(labelsize_key, 22))
labelsize = float(_conf_colorbar(labelsize_key, 18))
labelrotation = float(_conf_colorbar("labelrotation", 90))
if tick_values is not None:
cb.ax.set_yticklabels(
Expand Down
20 changes: 18 additions & 2 deletions autoarray/plot/yx.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ def plot_yx(
title: str = "",
xlabel: str = "",
ylabel: str = "",
xtick_suffix: str = "",
ytick_suffix: str = "",
label: Optional[str] = None,
color: str = "b",
color: str = "k",
linestyle: str = "-",
plot_axis_type: str = "linear",
# --- figure control (used only when ax is None) -----------------------------
Expand Down Expand Up @@ -129,7 +131,21 @@ def plot_yx(
ax.fill_between(x, y1, y2, alpha=0.3)

# --- labels ----------------------------------------------------------------
apply_labels(ax, title=title, xlabel=xlabel, ylabel=ylabel)
apply_labels(ax, title=title, xlabel=xlabel, ylabel=ylabel, is_subplot=not owns_figure)

# --- 3-point ticks with optional unit suffixes ----------------------------
from autoarray.plot.utils import _inward_ticks, _round_ticks, _conf_ticks

factor = _conf_ticks("extent_factor_2d", 0.75)

xlo, xhi = ax.get_xlim()
ylo, yhi = ax.get_ylim()
xticks = _round_ticks(_inward_ticks(xlo, xhi, factor, 3))
yticks = _round_ticks(_inward_ticks(ylo, yhi, factor, 3))
ax.set_xticks(xticks)
ax.set_xticklabels([f"{v:g}{xtick_suffix}" for v in xticks])
ax.set_yticks(yticks)
ax.set_yticklabels([f"{v:g}{ytick_suffix}" for v in yticks])

if label is not None:
ax.legend(fontsize=12)
Expand Down
Loading