|
| 1 | +"""Unit tests for Heatmap axis orientation in Sweep2D. |
| 2 | +
|
| 3 | +This test verifies that the x and y axes in the heatmap correctly correspond |
| 4 | +to the inner and outer sweep parameters respectively. |
| 5 | +""" |
| 6 | + |
| 7 | +import numpy as np |
| 8 | +import pytest |
| 9 | + |
| 10 | + |
| 11 | +class TestHeatmapAxisOrientation: |
| 12 | + """Test that heatmap data is correctly oriented with respect to sweep axes.""" |
| 13 | + |
| 14 | + def test_data_row_corresponds_to_outer_sweep_value(self, mock_parameters, fast_sweep_kwargs, qapp): |
| 15 | + """Test that data for a given outer sweep value is placed in the correct row. |
| 16 | +
|
| 17 | + The heatmap should display: |
| 18 | + - X axis (horizontal): inner sweep parameter values |
| 19 | + - Y axis (vertical): outer sweep parameter values |
| 20 | +
|
| 21 | + When data is collected at out_value = out_start (first outer step), |
| 22 | + it should appear at the bottom of the heatmap (row 0 in the data array |
| 23 | + when using PyQtGraph's default coordinate system with setRect). |
| 24 | +
|
| 25 | + Bug: Currently the row index is inverted, causing out_start data to |
| 26 | + appear at out_stop position and vice versa. |
| 27 | + """ |
| 28 | + from measureit.sweep.sweep2d import Sweep2D |
| 29 | + from measureit.visualization.heatmap_thread import Heatmap |
| 30 | + |
| 31 | + # Create a sweep with known parameters |
| 32 | + # Inner sweep: voltage from 0 to 1 with step 0.5 (3 points: 0, 0.5, 1) |
| 33 | + # Outer sweep: gate from -1 to 1 with step 1.0 (3 points: -1, 0, 1) |
| 34 | + in_params = [mock_parameters["voltage"], 0, 1, 0.5] |
| 35 | + out_params = [mock_parameters["gate"], -1, 1, 1.0] |
| 36 | + |
| 37 | + sweep = Sweep2D( |
| 38 | + in_params, |
| 39 | + out_params, |
| 40 | + outer_delay=0.1, |
| 41 | + **fast_sweep_kwargs, |
| 42 | + ) |
| 43 | + sweep.follow_param(mock_parameters["current"]) |
| 44 | + |
| 45 | + # Create heatmap and initialize its data structures |
| 46 | + heatmap = Heatmap(sweep) |
| 47 | + |
| 48 | + # Manually initialize the heatmap data structures (simulating create_figs) |
| 49 | + heatmap.res_in = 3 # 0, 0.5, 1 |
| 50 | + heatmap.res_out = 3 # -1, 0, 1 |
| 51 | + heatmap.in_keys = [0.0, 0.5, 1.0] |
| 52 | + heatmap.out_keys = [-1.0, 0.0, 1.0] |
| 53 | + heatmap.in_step = 0.5 |
| 54 | + heatmap.out_step = 1.0 |
| 55 | + heatmap.figs_set = True |
| 56 | + heatmap.param_surfaces = {} |
| 57 | + |
| 58 | + # Create a data dict to simulate data from the plotter |
| 59 | + # This represents completing an inner sweep at outer value = -1.0 (out_start) |
| 60 | + # The measurement values are [10, 20, 30] at inner values [0, 0.5, 1.0] |
| 61 | + data_dict = { |
| 62 | + "forward": ( |
| 63 | + np.array([0.0, 0.5, 1.0]), # x_data (inner sweep values) |
| 64 | + np.array([10.0, 20.0, 30.0]), # y_data (measured values) |
| 65 | + ), |
| 66 | + "param_index": 1, # Index of the parameter being measured |
| 67 | + "out_value": -1.0, # This is out_start |
| 68 | + } |
| 69 | + |
| 70 | + # Add the data to the heatmap |
| 71 | + heatmap.add_to_heatmap(data_dict) |
| 72 | + |
| 73 | + # Get the data array for this parameter |
| 74 | + data_array = heatmap.param_surfaces[1]["data"] |
| 75 | + |
| 76 | + # The data array has shape (res_out, res_in) = (3, 3) |
| 77 | + # Rows correspond to outer sweep values, columns to inner sweep values |
| 78 | + # |
| 79 | + # With setRect mapping: |
| 80 | + # - Row 0 should correspond to out_start = -1.0 (bottom of y-axis) |
| 81 | + # - Row 2 should correspond to out_stop = 1.0 (top of y-axis) |
| 82 | + # |
| 83 | + # So data collected at out_value = -1.0 should be in row 0. |
| 84 | + |
| 85 | + # Verify the data is in row 0 (corresponding to out_start = -1.0) |
| 86 | + # This is where the bug manifests: currently the data ends up in row 2 |
| 87 | + expected_row = 0 # out_start should map to row 0 |
| 88 | + |
| 89 | + # Check that row 0 contains the data we added |
| 90 | + assert data_array[expected_row, 0] == 10.0, \ |
| 91 | + f"Data at (row=0, col=0) should be 10.0 but got {data_array[expected_row, 0]}. " \ |
| 92 | + f"Row 0 should contain data for out_value=-1.0 (out_start)." |
| 93 | + assert data_array[expected_row, 1] == 20.0, \ |
| 94 | + f"Data at (row=0, col=1) should be 20.0 but got {data_array[expected_row, 1]}." |
| 95 | + assert data_array[expected_row, 2] == 30.0, \ |
| 96 | + f"Data at (row=0, col=2) should be 30.0 but got {data_array[expected_row, 2]}." |
| 97 | + |
| 98 | + # Also verify that other rows are still zero (data should NOT be in row 2) |
| 99 | + assert data_array[2, 0] == 0.0, \ |
| 100 | + f"Row 2 should be empty but contains {data_array[2, 0]}. " \ |
| 101 | + f"Bug: data for out_start is incorrectly placed at out_stop row." |
| 102 | + |
| 103 | + def test_multiple_outer_steps_correct_row_mapping(self, mock_parameters, fast_sweep_kwargs, qapp): |
| 104 | + """Test that multiple outer sweep steps map to correct rows. |
| 105 | +
|
| 106 | + For a sweep with outer values [-1, 0, 1]: |
| 107 | + - out_value = -1 (out_start) should map to row 0 (bottom) |
| 108 | + - out_value = 0 should map to row 1 (middle) |
| 109 | + - out_value = 1 (out_stop) should map to row 2 (top) |
| 110 | + """ |
| 111 | + from measureit.sweep.sweep2d import Sweep2D |
| 112 | + from measureit.visualization.heatmap_thread import Heatmap |
| 113 | + |
| 114 | + in_params = [mock_parameters["voltage"], 0, 1, 0.5] |
| 115 | + out_params = [mock_parameters["gate"], -1, 1, 1.0] |
| 116 | + |
| 117 | + sweep = Sweep2D( |
| 118 | + in_params, |
| 119 | + out_params, |
| 120 | + outer_delay=0.1, |
| 121 | + **fast_sweep_kwargs, |
| 122 | + ) |
| 123 | + sweep.follow_param(mock_parameters["current"]) |
| 124 | + |
| 125 | + heatmap = Heatmap(sweep) |
| 126 | + heatmap.res_in = 3 |
| 127 | + heatmap.res_out = 3 |
| 128 | + heatmap.in_keys = [0.0, 0.5, 1.0] |
| 129 | + heatmap.out_keys = [-1.0, 0.0, 1.0] |
| 130 | + heatmap.in_step = 0.5 |
| 131 | + heatmap.out_step = 1.0 |
| 132 | + heatmap.figs_set = True |
| 133 | + heatmap.param_surfaces = {} |
| 134 | + |
| 135 | + # Add data for each outer sweep step with distinguishable values |
| 136 | + test_cases = [ |
| 137 | + {"out_value": -1.0, "values": [100.0, 110.0, 120.0], "expected_row": 0}, |
| 138 | + {"out_value": 0.0, "values": [200.0, 210.0, 220.0], "expected_row": 1}, |
| 139 | + {"out_value": 1.0, "values": [300.0, 310.0, 320.0], "expected_row": 2}, |
| 140 | + ] |
| 141 | + |
| 142 | + for case in test_cases: |
| 143 | + data_dict = { |
| 144 | + "forward": ( |
| 145 | + np.array([0.0, 0.5, 1.0]), |
| 146 | + np.array(case["values"]), |
| 147 | + ), |
| 148 | + "param_index": 1, |
| 149 | + "out_value": case["out_value"], |
| 150 | + } |
| 151 | + heatmap.add_to_heatmap(data_dict) |
| 152 | + |
| 153 | + data_array = heatmap.param_surfaces[1]["data"] |
| 154 | + |
| 155 | + # Verify each row contains the correct data |
| 156 | + for case in test_cases: |
| 157 | + row = case["expected_row"] |
| 158 | + expected_values = case["values"] |
| 159 | + actual_values = [data_array[row, 0], data_array[row, 1], data_array[row, 2]] |
| 160 | + |
| 161 | + assert actual_values == expected_values, \ |
| 162 | + f"Row {row} (out_value={case['out_value']}) should contain {expected_values} " \ |
| 163 | + f"but got {actual_values}. The y-axis mapping is inverted." |
| 164 | + |
| 165 | + def test_inner_sweep_maps_to_columns(self, mock_parameters, fast_sweep_kwargs, qapp): |
| 166 | + """Test that inner sweep values correctly map to columns (x-axis). |
| 167 | +
|
| 168 | + For inner sweep values [0, 0.5, 1.0]: |
| 169 | + - in_value = 0 should map to column 0 (left) |
| 170 | + - in_value = 0.5 should map to column 1 (middle) |
| 171 | + - in_value = 1.0 should map to column 2 (right) |
| 172 | + """ |
| 173 | + from measureit.sweep.sweep2d import Sweep2D |
| 174 | + from measureit.visualization.heatmap_thread import Heatmap |
| 175 | + |
| 176 | + in_params = [mock_parameters["voltage"], 0, 1, 0.5] |
| 177 | + out_params = [mock_parameters["gate"], -1, 1, 1.0] |
| 178 | + |
| 179 | + sweep = Sweep2D( |
| 180 | + in_params, |
| 181 | + out_params, |
| 182 | + outer_delay=0.1, |
| 183 | + **fast_sweep_kwargs, |
| 184 | + ) |
| 185 | + sweep.follow_param(mock_parameters["current"]) |
| 186 | + |
| 187 | + heatmap = Heatmap(sweep) |
| 188 | + heatmap.res_in = 3 |
| 189 | + heatmap.res_out = 3 |
| 190 | + heatmap.in_keys = [0.0, 0.5, 1.0] |
| 191 | + heatmap.out_keys = [-1.0, 0.0, 1.0] |
| 192 | + heatmap.in_step = 0.5 |
| 193 | + heatmap.out_step = 1.0 |
| 194 | + heatmap.figs_set = True |
| 195 | + heatmap.param_surfaces = {} |
| 196 | + |
| 197 | + # Add data with specific values to verify column mapping |
| 198 | + # Value at each position uniquely identifies its (in_value, out_value) pair |
| 199 | + data_dict = { |
| 200 | + "forward": ( |
| 201 | + np.array([0.0, 0.5, 1.0]), # Inner sweep x values |
| 202 | + np.array([1.0, 2.0, 3.0]), # Measurement values |
| 203 | + ), |
| 204 | + "param_index": 1, |
| 205 | + "out_value": 0.0, # Middle row |
| 206 | + } |
| 207 | + heatmap.add_to_heatmap(data_dict) |
| 208 | + |
| 209 | + data_array = heatmap.param_surfaces[1]["data"] |
| 210 | + |
| 211 | + # The expected row for out_value=0.0 is row 1 (middle) |
| 212 | + # But due to the bug, it might be in a different row |
| 213 | + # Let's check column mapping is correct regardless of which row the data is in |
| 214 | + |
| 215 | + # Find which row has non-zero data |
| 216 | + non_zero_rows = np.where(np.any(data_array != 0, axis=1))[0] |
| 217 | + assert len(non_zero_rows) == 1, \ |
| 218 | + f"Expected exactly one row with data, found {len(non_zero_rows)}" |
| 219 | + |
| 220 | + data_row = non_zero_rows[0] |
| 221 | + |
| 222 | + # Verify column mapping: column 0 should have value 1.0, column 1 should have 2.0, etc. |
| 223 | + assert data_array[data_row, 0] == 1.0, \ |
| 224 | + f"Column 0 (in_value=0.0) should have value 1.0, got {data_array[data_row, 0]}" |
| 225 | + assert data_array[data_row, 1] == 2.0, \ |
| 226 | + f"Column 1 (in_value=0.5) should have value 2.0, got {data_array[data_row, 1]}" |
| 227 | + assert data_array[data_row, 2] == 3.0, \ |
| 228 | + f"Column 2 (in_value=1.0) should have value 3.0, got {data_array[data_row, 2]}" |
0 commit comments