Skip to content

Commit 6a3cb17

Browse files
authored
Merge pull request #31 from aerubanov/burn-in-param-input
Add burn-in param input
2 parents b431838 + 33a7e36 commit 6a3cb17

File tree

5 files changed

+333
-1
lines changed

5 files changed

+333
-1
lines changed

src/App.jsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ function App() {
3838
setInitialPosition2,
3939
setSeed2,
4040
burnIn,
41+
setBurnIn,
4142
rHat,
4243
ess,
4344
} = useSamplingController();
@@ -71,6 +72,8 @@ function App() {
7172
setUseSecondChain={setUseSecondChain}
7273
setInitialPosition2={setInitialPosition2}
7374
setSeed2={setSeed2}
75+
burnIn={burnIn}
76+
setBurnIn={setBurnIn}
7477
/>
7578
</div>
7679
<div className="App-main">

src/components/Controls.jsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ function Controls({
2828
setUseSecondChain,
2929
setInitialPosition2,
3030
setSeed2,
31+
burnIn,
32+
setBurnIn,
3133
}) {
3234
const [nSteps, setNSteps] = useState(params.steps || 10);
3335
const [draftLogP, setDraftLogP] = useState(logP);
@@ -40,6 +42,9 @@ function Controls({
4042
const [localY2, setLocalY2] = useState(initialPosition2?.y ?? 1);
4143
const [localSeed2, setLocalSeed2] = useState(seed2 || 43);
4244

45+
// Burn-in local state
46+
const [localBurnIn, setLocalBurnIn] = useState(burnIn);
47+
4348
// Sync draft with prop when it changes externally (e.g., on reset)
4449
useEffect(() => {
4550
setDraftLogP(logP);
@@ -59,6 +64,11 @@ function Controls({
5964
}
6065
}, [initialPosition2]);
6166

67+
// Sync burn-in state with props
68+
useEffect(() => {
69+
setLocalBurnIn(burnIn);
70+
}, [burnIn]);
71+
6272
const handleLogPChange = (e) => {
6373
setDraftLogP(e.target.value);
6474
};
@@ -137,6 +147,14 @@ function Controls({
137147
setSeed2(newSeed);
138148
};
139149

150+
const handleBurnInChange = (e) => {
151+
const value = parseInt(e.target.value, 10);
152+
setLocalBurnIn(value);
153+
if (!isNaN(value) && value >= 0) {
154+
setBurnIn(value);
155+
}
156+
};
157+
140158
const acceptanceRate =
141159
iterationCount > 0
142160
? ((acceptedCount / iterationCount) * 100).toFixed(1)
@@ -220,6 +238,21 @@ function Controls({
220238
/>
221239
</div>
222240

241+
<div className="control-group">
242+
<label htmlFor="burnin-input" className="control-label">
243+
Burn-in Samples
244+
</label>
245+
<input
246+
id="burnin-input"
247+
type="number"
248+
className="control-input"
249+
step="1"
250+
min="0"
251+
value={localBurnIn}
252+
onChange={handleBurnInChange}
253+
/>
254+
</div>
255+
223256
{/* Random Seed */}
224257
<div className="control-group">
225258
<div className="checkbox-group">
@@ -652,6 +685,8 @@ Controls.propTypes = {
652685
setUseSecondChain: PropTypes.func.isRequired,
653686
setInitialPosition2: PropTypes.func.isRequired,
654687
setSeed2: PropTypes.func.isRequired,
688+
burnIn: PropTypes.number.isRequired,
689+
setBurnIn: PropTypes.func.isRequired,
655690
};
656691

657692
export default Controls;

src/hooks/useSamplingController.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export default function useSamplingController() {
4141
const [ess, setEss] = useState(null);
4242

4343
// Visualization params
44-
const [burnIn] = useState(10);
44+
const [burnIn, setBurnIn] = useState(10);
4545

4646
// Refs to hold instances/values that don't trigger re-renders or need to be accessed in loops
4747
const logpInstanceRef = useRef(null);
@@ -382,6 +382,7 @@ export default function useSamplingController() {
382382
setInitialPosition2,
383383
setSeed2,
384384
burnIn,
385+
setBurnIn,
385386
rHat,
386387
ess,
387388
};

tests/components/Controls.test.jsx

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ describe('Controls Component', () => {
2929
setUseSecondChain: vi.fn(),
3030
setInitialPosition2: vi.fn(),
3131
setSeed2: vi.fn(),
32+
burnIn: 10,
33+
setBurnIn: vi.fn(),
3234
};
3335

3436
beforeEach(() => {
@@ -275,4 +277,63 @@ describe('Controls Component', () => {
275277
expect(screen.getByRole('button', { name: /reset/i })).toBeDisabled();
276278
});
277279
});
280+
281+
describe('Burn-in Parameter Input', () => {
282+
it('should render burn-in input with correct initial value', () => {
283+
const props = { ...mockProps, burnIn: 10, setBurnIn: vi.fn() };
284+
render(<Controls {...props} />);
285+
286+
const burnInInput = screen.getByLabelText(/burn-in samples/i);
287+
expect(burnInInput).toBeInTheDocument();
288+
expect(burnInInput.value).toBe('10');
289+
});
290+
291+
it('should call setBurnIn when burn-in value changes', () => {
292+
const setBurnIn = vi.fn();
293+
const props = { ...mockProps, burnIn: 10, setBurnIn };
294+
render(<Controls {...props} />);
295+
296+
const burnInInput = screen.getByLabelText(/burn-in samples/i);
297+
fireEvent.change(burnInInput, { target: { value: '15' } });
298+
299+
expect(setBurnIn).toHaveBeenCalledWith(15);
300+
});
301+
302+
it('should have correct input attributes for validation', () => {
303+
const props = { ...mockProps, burnIn: 10, setBurnIn: vi.fn() };
304+
render(<Controls {...props} />);
305+
306+
const burnInInput = screen.getByLabelText(/burn-in samples/i);
307+
expect(burnInInput).toHaveAttribute('type', 'number');
308+
expect(burnInInput).toHaveAttribute('min', '0');
309+
expect(burnInInput).toHaveAttribute('step', '1');
310+
});
311+
312+
it('should sync local state when burnIn prop changes', () => {
313+
const props = { ...mockProps, burnIn: 10, setBurnIn: vi.fn() };
314+
const { rerender } = render(<Controls {...props} />);
315+
316+
const burnInInput = screen.getByLabelText(/burn-in samples/i);
317+
expect(burnInInput.value).toBe('10');
318+
319+
// External change (e.g., from hook state update)
320+
rerender(<Controls {...props} burnIn={20} />);
321+
322+
expect(burnInInput.value).toBe('20');
323+
});
324+
325+
it('should not call setBurnIn with invalid values', () => {
326+
const setBurnIn = vi.fn();
327+
const props = { ...mockProps, burnIn: 10, setBurnIn };
328+
render(<Controls {...props} />);
329+
330+
const burnInInput = screen.getByLabelText(/burn-in samples/i);
331+
332+
// Try to set non-numeric value
333+
fireEvent.change(burnInInput, { target: { value: 'abc' } });
334+
335+
// setBurnIn should not be called with NaN
336+
expect(setBurnIn).not.toHaveBeenCalled();
337+
});
338+
});
278339
});

0 commit comments

Comments
 (0)