Skip to content
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

Add console text output tab #262

Merged
merged 4 commits into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from 2 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
30 changes: 25 additions & 5 deletions gui/src/app/SamplerOutputView/SamplerOutputView.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { Download } from "@mui/icons-material";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import IconButton from "@mui/material/IconButton";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
Expand All @@ -7,8 +10,6 @@ import {
SuccessBorderedTableRow,
SuccessColoredTableHead,
} from "@SpComponents/StyledTables";
import Button from "@mui/material/Button";
import { IconButton } from "@mui/material";
import HistsView from "@SpComponents/HistsView";
import SummaryView from "@SpComponents/SummaryView";
import TabWidget from "@SpComponents/TabWidget";
Expand All @@ -26,15 +27,17 @@ type SamplerOutputViewProps = {
const SamplerOutputView: FunctionComponent<SamplerOutputViewProps> = ({
latestRun,
}) => {
const { draws, paramNames, computeTimeSec, samplingOpts } = latestRun;
const { samplingOpts, runResult } = latestRun;
if (!runResult || !samplingOpts) return <span />;
const { draws, paramNames, computeTimeSec, consoleText } = runResult;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this line doesn't die when runResult is undefined, then I think it might be clearer to keep the early <span /> return closer to the actual-component return, i.e. move the current l31 to l33.


if (!draws || !paramNames || !samplingOpts) return <span />;
return (
<DrawsDisplay
draws={draws}
paramNames={paramNames}
computeTimeSec={computeTimeSec}
samplingOpts={samplingOpts}
consoleText={consoleText}
/>
);
};
Expand All @@ -44,13 +47,15 @@ type DrawsDisplayProps = {
paramNames: string[];
computeTimeSec: number | undefined;
samplingOpts: SamplingOpts;
consoleText: string;
};

const DrawsDisplay: FunctionComponent<DrawsDisplayProps> = ({
draws,
paramNames,
computeTimeSec,
samplingOpts,
consoleText,
}) => {
const numChains = samplingOpts.num_chains;

Expand All @@ -68,7 +73,9 @@ const DrawsDisplay: FunctionComponent<DrawsDisplayProps> = ({
}, [draws, numChains]);

return (
<TabWidget labels={["Summary", "Draws", "Histograms", "Trace plots"]}>
<TabWidget
labels={["Summary", "Draws", "Histograms", "Trace plots", "Console"]}
>
<SummaryView
draws={draws}
paramNames={paramNames}
Expand All @@ -92,10 +99,23 @@ const DrawsDisplay: FunctionComponent<DrawsDisplayProps> = ({
paramNames={paramNames}
drawChainIds={drawChainIds}
/>
<ConsoleOutput text={consoleText} />
</TabWidget>
);
};

type ConsoleOutputProps = {
text: string;
};

const ConsoleOutput: FunctionComponent<ConsoleOutputProps> = ({ text }) => {
return (
<Box className="stdout" color="info.dark">
<pre>{text}</pre>
</Box>
);
};

WardBrian marked this conversation as resolved.
Show resolved Hide resolved
type DrawsViewProps = {
draws: number[][];
paramNames: string[];
Expand Down
5 changes: 3 additions & 2 deletions gui/src/app/Scripting/Analysis/useAnalysisState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ const useAnalysisState = (latestRun: StanRun) => {

useEffect(() => {
clearOutputDivs(consoleRef, imagesRef);
}, [latestRun.draws]);
}, [latestRun.runResult?.draws]);

const { draws, paramNames, samplingOpts, status: samplerStatus } = latestRun;
const { runResult, samplingOpts, status: samplerStatus } = latestRun;
const { draws, paramNames } = runResult || {};
const numChains = samplingOpts?.num_chains;
const spData = useMemo(() => {
if (samplerStatus === "completed" && draws && numChains && paramNames) {
Expand Down
13 changes: 13 additions & 0 deletions gui/src/app/StanSampler/StanModelWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export type StanModelReplyMessage =
draws: number[][];
paramNames: string[];
error: null;
consoleText: string;
}
| {
purpose: Replies.Progress;
Expand Down Expand Up @@ -77,8 +78,16 @@ const parseProgress = (msg: string): Progress => {
return { chain, iteration, totalIterations, percent, warmup };
};

let consoleText = "";
const progressPrintCallback = (msg: string) => {
if (!msg) {
return;
}
if (!msg.startsWith("Chain") && !msg.startsWith("Iteration:")) {
// storing this has a not-insignificant overhead when a model
// has print statements, but is much faster than posting
// every single line to the main thread
consoleText += msg + "\n";
console.log(msg);
return;
}
Expand Down Expand Up @@ -116,13 +125,15 @@ self.onmessage = (e: MessageEvent<StanModelRequestMessage>) => {
return;
}
try {
consoleText = "";
const { paramNames, draws } = model.sample(e.data.sampleConfig);
// TODO? use an ArrayBuffer so we can transfer without serialization cost
postReply({
purpose: Replies.StanReturn,
draws,
paramNames,
error: null,
consoleText,
});
} catch (e: any) {
postReply({ purpose: Replies.StanReturn, error: e.toString() });
Expand All @@ -138,13 +149,15 @@ self.onmessage = (e: MessageEvent<StanModelRequestMessage>) => {
return;
}
try {
consoleText = "";
WardBrian marked this conversation as resolved.
Show resolved Hide resolved
const { draws, paramNames } = model.pathfinder(e.data.pathfinderConfig);
// TODO? use an ArrayBuffer so we can transfer without serialization cost
postReply({
purpose: Replies.StanReturn,
draws,
paramNames,
error: null,
consoleText,
});
} catch (e: any) {
postReply({ purpose: Replies.StanReturn, error: e.toString() });
Expand Down
1 change: 1 addition & 0 deletions gui/src/app/StanSampler/StanSampler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ class StanSampler {
draws: e.data.draws,
paramNames: e.data.paramNames,
computeTimeSec: Date.now() / 1000 - this.#samplingStartTimeSec,
consoleText: e.data.consoleText,
});
}
break;
Expand Down
19 changes: 13 additions & 6 deletions gui/src/app/StanSampler/useStanSampler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@ export type StanRun = {
progress?: Progress;
samplingOpts?: SamplingOpts;
data?: string;
draws?: number[][];
paramNames?: string[];
computeTimeSec?: number;
runResult?: {
consoleText: string;
draws: number[][];
paramNames: string[];
computeTimeSec: number;
};
};

const initialStanRun: StanRun = {
Expand Down Expand Up @@ -43,6 +46,7 @@ export type StanRunAction =
draws: number[][];
paramNames: string[];
computeTimeSec: number;
consoleText: string;
};

export const StanRunReducer = (
Expand Down Expand Up @@ -73,9 +77,12 @@ export const StanRunReducer = (
return {
...state,
status: "completed",
draws: action.draws,
paramNames: action.paramNames,
computeTimeSec: action.computeTimeSec,
runResult: {
draws: action.draws,
paramNames: action.paramNames,
computeTimeSec: action.computeTimeSec,
consoleText: action.consoleText,
},
};
default:
return unreachable(action);
Expand Down
35 changes: 24 additions & 11 deletions gui/test/app/StanSampler/useStanSampler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,9 @@ describe("useStanSampler", () => {

await waitFor(() => {
expect(result.current.latestRun.status).toBe("completed");
expect(result.current.latestRun.paramNames).toEqual(mockedParamNames);
expect(result.current.latestRun.runResult?.paramNames).toEqual(
mockedParamNames,
);
});
expect(mockedStderr).not.toHaveBeenCalled();
});
Expand Down Expand Up @@ -178,7 +180,9 @@ describe("useStanSampler", () => {

await waitFor(() => {
expect(result.current.latestRun.status).toBe("completed");
expect(result.current.latestRun.paramNames).toEqual(mockedParamNames);
expect(result.current.latestRun.runResult?.paramNames).toEqual(
mockedParamNames,
);
});

expect(mockedStderr).not.toHaveBeenCalled();
Expand Down Expand Up @@ -206,17 +210,21 @@ describe("useStanSampler", () => {
describe("outputs", () => {
test("undefined sampler returns undefined", () => {
const { result } = renderHook(() => useStanSampler(undefined));
expect(result.current.latestRun.draws).toBeUndefined();
expect(result.current.latestRun.paramNames).toBeUndefined();
expect(result.current.latestRun.computeTimeSec).toBeUndefined();
expect(result.current.latestRun.runResult?.draws).toBeUndefined();
expect(result.current.latestRun.runResult?.paramNames).toBeUndefined();
expect(
result.current.latestRun.runResult?.computeTimeSec,
).toBeUndefined();
});

test("sampling changes output", async () => {
const { result } = await loadedSampler();

expect(result.current.latestRun.draws).toBeUndefined();
expect(result.current.latestRun.paramNames).toBeUndefined();
expect(result.current.latestRun.computeTimeSec).toBeUndefined();
expect(result.current.latestRun.runResult?.draws).toBeUndefined();
expect(result.current.latestRun.runResult?.paramNames).toBeUndefined();
expect(
result.current.latestRun.runResult?.computeTimeSec,
).toBeUndefined();
expect(result.current.latestRun.samplingOpts).toBeUndefined();

const testingSamplingOpts = {
Expand All @@ -229,10 +237,15 @@ describe("useStanSampler", () => {
});

await waitFor(() => {
expect(result.current.latestRun.draws).toEqual(mockedDraws);
expect(result.current.latestRun.paramNames).toEqual(mockedParamNames);
expect(result.current.latestRun.samplingOpts).toBe(testingSamplingOpts);
expect(result.current.latestRun.computeTimeSec).toBeDefined();
expect(result.current.latestRun.runResult).toBeDefined();
expect(result.current.latestRun.runResult?.draws).toEqual(mockedDraws);
expect(result.current.latestRun.runResult?.paramNames).toEqual(
mockedParamNames,
);
expect(
result.current.latestRun.runResult?.computeTimeSec,
).toBeDefined();
});

expect(result.current.latestRun.status).toBe("completed");
Expand Down
Loading