diff --git a/visualisation/CLAUDE.md b/visualisation/CLAUDE.md new file mode 100644 index 00000000..c4fb788f --- /dev/null +++ b/visualisation/CLAUDE.md @@ -0,0 +1,171 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## What This Is + +VTK-based visualisation and analysis tools for Grid lattice QCD eigenvector density and HMC force data. All programmes link against both Grid (for reading Scidac/ILDG lattice files) and VTK (for rendering). + +## Build + +```bash +cd /Users/peterboyle/QCD/AmSC/Grid/visualisation/build +cmake .. -DVTK_DIR=$HOME/QCD/vtk/VTK-9.4.2-install/lib/cmake/vtk-9.4 +make # e.g. make ControlledVisualise5D +``` + +All executables are built as macOS bundles (`.app`) except `ForceAnalysis`, `FindPeak`, and `DumpField`. + +## Programmes + +### ControlledVisualise5D + +Interactive VTK renderer for 5D DWF eigenvector density (`LatticeComplexD`). Driven via named pipe `/tmp/visualise_cmd`. + +**Launch script**: `/Volumes/X9Pro/visualisation/Grid/visualisation/build/Hdwf_1_long/visualise_controlled.sh` + +**Wire protocol** (one command per line to `/tmp/visualise_cmd`): + +| Command | Effect | +|---------|--------| +| `file ` / `file +N` / `file -N` | Jump to file by index or relative | +| `slice ` / `+N` / `-N` | Set or shift slice coordinate in dimension dim | +| `spin ` | Continuous azimuth rotation at deg/tick (100ms tick); `spin 0` stops | +| `azimuth ` | Single azimuth rotation step | +| `elevation ` | Single elevation rotation step | +| `zoom ` | Camera dolly (>1 = in) | +| `iso ` | Isosurface threshold in RMS units | +| `status` | Print current state | +| `quit` | Exit | + +**Dimension indices for 5D DWF grid** (`--grid 48.32.32.32.32`): + +| dim | axis | size | +|-----|------|------| +| 0 | s (Ls) | 48 | +| 1 | x | 32 | +| 2 | y | 32 | +| 3 | z | 32 | +| 4 | t | 32 | + +**MD time mapping** for trajectory 702 (241 files, τ=3.3–4.0): +- File index N → τ = 3.300000 + N × (1/480) +- τ → file index = round((τ − 3.3) × 480) + +**Display axes**: `--xyz 0.3.4` shows s, z, t. The `--slice` argument sets initial values for all dims; dims not in `--xyz`, `--sum`, or `--loop` are the fixed slice dimensions (x=dim1, y=dim2 with `--xyz 0.3.4`). + +**Spin**: Implemented via `g_spinDeg` global applied on every 100ms poll timer tick inside `CommandHandler::Execute()`. Does not flood the pipe. + +### FindPeak + +Reads a `LatticeComplexD` Scidac file, prints the top-N sites by real value to stderr. + +```bash +./FindPeak --grid 48.32.32.32.32 --mpi 1.1.1.1.1 2>peaks.txt +``` + +Key result: At τ=3.670833 the tunneling hotsite on the s=0 wall is (x=21, y=24, z=2, t=23). + +### ForceAnalysis + +Reads 4D `LatticeComplexD` force snapshot files (Shuhei's snapshots at `/Volumes/X9Pro/visualisation/Shuhei/snapshots/`). Outputs TSV of RMS and hotsite value per file to stderr. + +```bash +./ForceAnalysis --grid 32.32.32.32 --mpi 1.1.1 --hotsite 21.24.2.23 \ + 2>force.tsv 1>/dev/null +``` + +Force components: `Gauge_lat`, `Gauge_smr`, `Jacobian_smr`, `Ferm0047_lat`, `Ferm0047_smr`. + +### DumpField + +Reads a `LatticeComplexD` and dumps via Grid's `<<` operator to stdout for verification. + +### TranscriptToVideo + +Renders a conversation transcript to an MP4 video (1280×720, 10 fps) with a typewriter animation effect, scrolling history, and optional captions. Does **not** link against Grid — pure VTK only. + +#### Transcript format + +``` +[USER] First question text, possibly +continuing on the next line. + +A blank line within a turn creates a paragraph break (visual spacer). + +[ASSISTANT] Response text. +Multiple continuation lines are preserved +as separate display lines, not merged. + +[CAPTION] Caption text shown at bottom of screen in white italic. +[CAPTION] (whitespace-only body clears the caption) +[USER] Next question... +``` + +- Lines beginning `[USER]`, `[ASSISTANT]`, `[CAPTION]` start a new turn. +- Continuation lines (no `[TAG]` prefix) are joined with `\n` — each becomes its own wrapped display line. +- Blank lines within a turn become paragraph-break spacers. +- Markdown emphasis markers (`**`, `*`, `` ` ``) are stripped automatically. +- UTF-8 smart quotes, em-dashes, ellipses, arrows are transliterated to ASCII. + +#### Usage + +```bash +cd /Users/peterboyle/QCD/AmSC/Grid/visualisation/build +# Set runtime library paths first (see Runtime Environment below) +./TranscriptToVideo +``` + +Transcript files live in `/Users/peterboyle/QCD/AmSC/Grid/visualisation/` (e.g. `transcript`, `transcript2`, `transcript3`). + +#### Visual layout + +| Element | Detail | +|---------|--------| +| Background | Near-black navy `(0.04, 0.04, 0.10)` | +| `[USER]` text | Gold `(1.00, 0.84, 0.00)` | +| `[ASSISTANT]` text | Steel blue `(0.68, 0.85, 0.90)` | +| History | Up to 18 lines; brightness fades linearly from 0.85 (newest) to 0.20 (oldest) | +| Caption | Arial italic 20pt white with shadow, centred at bottom | +| Progress bar | Blue, top of frame | +| Typewriter speed | 50 chars/sec (5 chars/frame at 10 fps) | +| Pause between lines | 3 frames (0.3 s) | +| Word-wrap column | 60 chars (body only, after prefix) | + +#### Key implementation notes + +- **Persistent render context**: a single `vtkRenderWindow` is created once and reused for all frames. Creating a new window per frame exhausts the macOS Metal GPU command buffer after ~33 frames (`MTLCommandBufferErrorDomain Code=8`). +- **`SanitiseASCII()`**: replaces multi-byte UTF-8 sequences before passing to VTK's font renderer (which crashes on non-ASCII input). +- Output format is MP4 via `vtkFFMPEGWriter`. `SetOffScreenRendering(1)` is required for headless rendering. + +## Runtime Environment + +All executables in `build/` require Spack-installed HDF5/FFTW/GMP/MPFR on the dynamic linker path: + +```bash +SPACK=/Users/peterboyle/QCD/Spack/spack/opt/spack/darwin-m1 +export DYLD_LIBRARY_PATH=\ +$SPACK/hdf5-1.14.6-2265ms4kymgw6hcnwi6vqehslyfv74t4/lib:\ +$SPACK/fftw-3.3.10-aznn6h3nac5cycidlhrhgjxvntpcbg57/lib:\ +$SPACK/gmp-6.3.0-cwiz4n7ww33fnb3aban2iup4orcr6c7i/lib:\ +$SPACK/mpfr-4.2.1-exgbz4qshmet6tmmuttdewdlunfvtrlb/lib:\ +$DYLD_LIBRARY_PATH +``` + +(These paths are also set by the ControlledVisualise5D launch script.) + +## Key Physics Context + +See `/Volumes/X9Pro/visualisation/analysis_notes_20260407.md` for full analysis. Summary: + +- Near-zero mode of H_DWF localises on the two walls (s=0 and s=47) of the 5D domain wall geometry +- Topology change transfers norm between walls, mediated by a near-zero mode of H_w (Hermitian Wilson at m=−1.8) +- Tunneling hotsite on s=0 wall: (x=21, y=24, z=2, t=23); s=47 wall: (x=4, y=8, z=0, t=20) +- Light fermion pseudofermion force (Ferm0047_smr) peaks at ~20× RMS at the hotsite during tunneling — this is the restoring force that causes topological bounces + +## Grid/VTK interaction notes + +- Grid log messages go to stdout; all data output in analysis programmes uses stderr to avoid interleaving +- `TensorRemove()` is required when extracting a scalar from `peekSite()` result: `real(TensorRemove(peekSite(field, site)))` +- For runtime-determined grid dimensionality use `GridDefaultSimd(latt_size.size(), vComplex::Nsimd())` +- DYLD_LIBRARY_PATH must include Spack HDF5/FFTW/GMP/MPFR paths (see launch script) diff --git a/visualisation/ControlledVisualise5D.cxx b/visualisation/ControlledVisualise5D.cxx index c1890917..2ecb04f3 100644 --- a/visualisation/ControlledVisualise5D.cxx +++ b/visualisation/ControlledVisualise5D.cxx @@ -43,8 +43,6 @@ #include #include #include -#include -#include #include #define MPEG @@ -61,7 +59,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -88,6 +88,71 @@ static std::mutex g_cmdMutex; static std::atomic g_running{true}; static double g_spinDeg = 0.0; // degrees per poll tick; 0 = stopped +// ─── MPEG recording state ───────────────────────────────────────────────────── +static bool g_recording = false; +static vtkFFMPEGWriter* g_mpegWriter = nullptr; +static vtkWindowToImageFilter* g_imageFilter = nullptr; +static std::string g_recordingFile; // AVI filename for mux step + +// ─── Audio state (PCM audio track, synced to video frames) ─────────────────── +static const int AUDIO_RATE = 44100; +static const double BEEP_FREQ = 800.0; +static const int BEEP_SAMPLES = AUDIO_RATE * 4 / 100; // 40ms beep +static std::vector g_audioBuffer; +static int g_beepRemaining = 0; +static double g_beepPhase = 0.0; +static int g_samplesPerFrame = AUDIO_RATE / 10; // updated at record start + +// Write one video frame worth of audio samples (beep or silence) to the buffer. +static void GenerateAudioFrame() +{ + for (int i = 0; i < g_samplesPerFrame; i++) { + int16_t s = 0; + if (g_beepRemaining > 0) { + int pos = BEEP_SAMPLES - g_beepRemaining; + double env = 1.0; + int fade = AUDIO_RATE / 100; // 10ms fade + if (pos < fade) env = (double)pos / fade; + if (g_beepRemaining < fade) env = (double)g_beepRemaining / fade; + s = (int16_t)(16000.0 * env * std::sin(2.0 * M_PI * BEEP_FREQ * g_beepPhase / AUDIO_RATE)); + g_beepPhase += 1.0; + --g_beepRemaining; + } else { + g_beepPhase = 0.0; + } + g_audioBuffer.push_back(s); + } +} + +static void TriggerBeep() { g_beepRemaining = BEEP_SAMPLES; } + +// Simple mono 16-bit PCM WAV writer. +static void WriteWAV(const std::string& path, const std::vector& buf, int rate) +{ + std::ofstream f(path, std::ios::binary); + int dataBytes = (int)(buf.size() * 2); + int chunkSize = 36 + dataBytes; + int byteRate = rate * 2; + f.write("RIFF", 4); f.write((char*)&chunkSize, 4); + f.write("WAVE", 4); + f.write("fmt ", 4); + int fmtSz = 16; f.write((char*)&fmtSz, 4); + int16_t pcm = 1; f.write((char*)&pcm, 2); + int16_t ch = 1; f.write((char*)&ch, 2); + f.write((char*)&rate, 4); + f.write((char*)&byteRate, 4); + int16_t blk = 2; f.write((char*)&blk, 2); + int16_t bps = 16; f.write((char*)&bps, 2); + f.write("data", 4); f.write((char*)&dataBytes, 4); + f.write((char*)buf.data(), dataBytes); +} + +// Play a short audible beep on the local machine (non-blocking). +static void PlayBeepAudible() +{ + system("afplay /System/Library/Sounds/Tink.aiff -v 0.4 &"); +} + // ─── Grid I/O ───────────────────────────────────────────────────────────────── template void readFile(T& out, const std::string& fname) @@ -295,29 +360,36 @@ private: const std::string& path = files[file]; std::string tau = path.substr(path.rfind('_') + 1); std::stringstream ss; - ss << "tau = " << tau << "\nSlice " << Slice << "\nLoop " << loop_coor; + ss << "tau = " << tau << "\nSlice " << Slice; text->SetInput(ss.str().c_str()); } }; -// ─── SliderCallback ─────────────────────────────────────────────────────────── -class SliderCallback : public vtkCommand -{ -public: - static SliderCallback* New() { return new SliderCallback; } - virtual void Execute(vtkObject* caller, unsigned long, void*) - { - vtkSliderWidget* sw = vtkSliderWidget::SafeDownCast(caller); - if (sw) contour = ((vtkSliderRepresentation*)sw->GetRepresentation())->GetValue(); - fu->posExtractor->SetValue(0, contour * fu->rms); - fu->negExtractor->SetValue(0, -contour * fu->rms); - fu->posExtractor->Modified(); - fu->negExtractor->Modified(); +// ─── Typewriter caption state ───────────────────────────────────────────────── +// User caption (gold, upper line) — cleared on new user: instruction +static std::string g_userCaptionFull; +static size_t g_userCaptionPos = 0; +// Claude caption (light blue, lower line) — cleared when user: arrives +static std::string g_claudeCaptionFull; +static size_t g_claudeCaptionPos = 0; +static int g_captionTick = 0; +static const int g_captionRate = 1; // ticks per character (1 x 100ms = 10 chars/sec) + +static std::string WrapText(const std::string& s, int maxCols = 45) { + std::istringstream words(s); + std::string word, line, result; + while (words >> word) { + if (!line.empty() && (int)(line.size() + 1 + word.size()) > maxCols) { + result += line + "\n"; + line = word; + } else { + if (!line.empty()) line += " "; + line += word; + } } - static double contour; - FrameUpdater* fu; -}; -double SliderCallback::contour = 1.0; + if (!line.empty()) result += line; + return result; +} // ─── CommandHandler ─────────────────────────────────────────────────────────── // Minimal parser for the wire protocol. Natural-language interpretation @@ -331,15 +403,22 @@ public: vtkCamera* camera; vtkRenderer* renderer; vtkRenderWindowInteractor* iren; - vtkSliderRepresentation2D* sliderRep; + vtkTextActor* captionActor = nullptr; // claude (light blue, lower) + vtkTextActor* userCaptionActor = nullptr; // user (gold, upper) int pollTimerId = -1; double isosurfaceLevel = 1.0; // in RMS units + void CaptureFrame() { + if (g_recording && g_mpegWriter && g_imageFilter) { + GenerateAudioFrame(); + g_imageFilter->Modified(); + g_mpegWriter->Write(); + } + } + void SetIsosurface(double level) { isosurfaceLevel = std::max(0.0, std::min(10.0, level)); - sliderRep->SetValue(isosurfaceLevel); - SliderCallback::contour = isosurfaceLevel; fu->posExtractor->SetValue(0, isosurfaceLevel * fu->rms); fu->negExtractor->SetValue(0, -isosurfaceLevel * fu->rms); fu->posExtractor->Modified(); @@ -417,6 +496,96 @@ public: std::cout << "[cmd] spin rate = " << g_spinDeg << " deg/tick" << std::endl; } + // ── caption user: / caption claude: / caption ───────── + // user: clears both lines, types user text (gold) on upper line. + // claude: keeps user line, types response (light blue) on lower line. + // caption alone clears both immediately. + else if (verb == "caption") { + std::string rest; + std::getline(iss, rest); + if (!rest.empty() && rest[0] == ' ') rest = rest.substr(1); + if (rest.empty()) { + g_userCaptionFull = ""; g_userCaptionPos = 0; + g_claudeCaptionFull = ""; g_claudeCaptionPos = 0; + g_captionTick = 0; + if (userCaptionActor) userCaptionActor->SetInput(""); + if (captionActor) captionActor->SetInput(""); + iren->GetRenderWindow()->Render(); CaptureFrame(); + } else if (rest.substr(0,5) == "user:") { + // New instruction: clear both, start typing user text + g_claudeCaptionFull = ""; g_claudeCaptionPos = 0; + g_userCaptionFull = WrapText(rest); g_userCaptionPos = 0; + g_captionTick = 0; + if (userCaptionActor) userCaptionActor->SetInput(""); + if (captionActor) captionActor->SetInput(""); + iren->GetRenderWindow()->Render(); CaptureFrame(); + } else { + // claude: or unlabelled — keep user line, type below + g_claudeCaptionFull = WrapText(rest); g_claudeCaptionPos = 0; + g_captionTick = 0; + } + } + + // ── record start / record stop ──────────────────────────── + else if (verb == "record") { +#ifdef MPEG + std::string sub; + if (!(iss >> sub)) { std::cout << "[cmd] record: expected start or stop" << std::endl; return; } + if (sub == "stop") { + if (g_recording && g_mpegWriter) { + g_mpegWriter->End(); + g_mpegWriter->Delete(); g_mpegWriter = nullptr; + g_imageFilter->Delete(); g_imageFilter = nullptr; + g_recording = false; + std::cout << "[cmd] recording stopped: " << g_recordingFile << std::endl; + // Write WAV and mux to MP4 + std::string wavFile = g_recordingFile + ".wav"; + WriteWAV(wavFile, g_audioBuffer, AUDIO_RATE); + g_audioBuffer.clear(); + std::string mp4File = g_recordingFile; + if (mp4File.size() > 4 && mp4File.substr(mp4File.size()-4) == ".avi") + mp4File = mp4File.substr(0, mp4File.size()-4) + ".mp4"; + else + mp4File += ".mp4"; + std::string cmd = "ffmpeg -y -i \"" + g_recordingFile + "\" -i \"" + wavFile + + "\" -c:v copy -c:a aac -shortest \"" + mp4File + "\" 2>/dev/null"; + int ret = system(cmd.c_str()); + if (ret == 0) { + std::cout << "[cmd] muxed output: " << mp4File << std::endl; + unlink(wavFile.c_str()); // clean up intermediate WAV + } else { + std::cout << "[cmd] mux failed (ffmpeg not found?). WAV kept: " << wavFile << std::endl; + } + } else { + std::cout << "[cmd] not recording" << std::endl; + } + } else if (sub == "start") { + std::string fname = "recording.avi"; + iss >> fname; + if (g_recording) { std::cout << "[cmd] already recording" << std::endl; return; } + g_recordingFile = fname; + g_audioBuffer.clear(); + g_samplesPerFrame = AUDIO_RATE / std::max(1, g_framerate); + g_beepRemaining = 0; + g_beepPhase = 0.0; + g_imageFilter = vtkWindowToImageFilter::New(); + g_imageFilter->SetInput(iren->GetRenderWindow()); + g_imageFilter->SetInputBufferTypeToRGB(); + g_mpegWriter = vtkFFMPEGWriter::New(); + g_mpegWriter->SetFileName(fname.c_str()); + g_mpegWriter->SetRate(g_framerate); + g_mpegWriter->SetInputConnection(g_imageFilter->GetOutputPort()); + g_mpegWriter->Start(); + g_recording = true; + std::cout << "[cmd] recording started: " << fname << std::endl; + } else { + std::cout << "[cmd] record: unknown subcommand '" << sub << "'" << std::endl; + } +#else + std::cout << "[cmd] record: MPEG support not compiled" << std::endl; +#endif + } + // ── iso ────────────────────────────────────────────────────── else if (verb == "iso") { double val; @@ -481,6 +650,37 @@ public: for (const auto& line : pending) { std::cout << "[cmd] >> " << line << std::endl; RunLine(line); + // CaptureFrame() called inside RunLine for caption; for other + // rendering commands capture here (duplicate Modified() is harmless) + CaptureFrame(); + } + + // Typewriter: advance one character every g_captionRate ticks. + // User line types first; claude line starts once user line is complete. + bool typing = (g_userCaptionPos < g_userCaptionFull.size()) || + (g_claudeCaptionPos < g_claudeCaptionFull.size()); + if (typing) { + if (++g_captionTick >= g_captionRate) { + g_captionTick = 0; + bool rendered = false; + if (g_userCaptionPos < g_userCaptionFull.size()) { + ++g_userCaptionPos; + if (userCaptionActor) + userCaptionActor->SetInput(g_userCaptionFull.substr(0, g_userCaptionPos).c_str()); + PlayBeepAudible(); + TriggerBeep(); + rendered = true; + } else if (g_claudeCaptionPos < g_claudeCaptionFull.size()) { + ++g_claudeCaptionPos; + if (captionActor) + captionActor->SetInput(g_claudeCaptionFull.substr(0, g_claudeCaptionPos).c_str()); + rendered = true; + } + if (rendered) { + iren->GetRenderWindow()->Render(); + CaptureFrame(); + } + } } // Apply continuous spin (if active) at poll-timer rate @@ -488,6 +688,7 @@ public: camera->Azimuth(g_spinDeg); renderer->ResetCameraClippingRange(); iren->GetRenderWindow()->Render(); + CaptureFrame(); } } }; @@ -603,11 +804,33 @@ int main(int argc, char* argv[]) vtkNew TextT; TextT->SetInput("Initialising..."); - TextT->SetPosition(0, 0.7*1025); + TextT->SetPosition(10, 920); TextT->GetTextProperty()->SetFontSize(24); TextT->GetTextProperty()->SetColor(colors->GetColor3d("Gold").GetData()); - aRenderer->AddActor(TextT); aRenderer->AddActor(outline); + // Claude response caption (light blue, lower line) + vtkNew CaptionT; + CaptionT->SetInput(""); + CaptionT->SetPosition(512, 38); + CaptionT->GetTextProperty()->SetFontSize(32); + CaptionT->GetTextProperty()->SetColor(0.6, 0.9, 1.0); + CaptionT->GetTextProperty()->SetJustificationToCentered(); + CaptionT->GetTextProperty()->SetBackgroundColor(0.0, 0.0, 0.0); + CaptionT->GetTextProperty()->SetBackgroundOpacity(0.6); + CaptionT->GetTextProperty()->BoldOn(); + + // User instruction caption (gold, upper line) + vtkNew UserCaptionT; + UserCaptionT->SetInput(""); + UserCaptionT->SetPosition(512, 82); + UserCaptionT->GetTextProperty()->SetFontSize(32); + UserCaptionT->GetTextProperty()->SetColor(1.0, 0.85, 0.0); + UserCaptionT->GetTextProperty()->SetJustificationToCentered(); + UserCaptionT->GetTextProperty()->SetBackgroundColor(0.0, 0.0, 0.0); + UserCaptionT->GetTextProperty()->SetBackgroundOpacity(0.6); + UserCaptionT->GetTextProperty()->BoldOn(); + + aRenderer->AddActor(TextT); aRenderer->AddActor(CaptionT); aRenderer->AddActor(UserCaptionT); aRenderer->AddActor(outline); aRenderer->AddActor(pos); aRenderer->AddActor(neg); vtkNew fu; @@ -627,34 +850,14 @@ int main(int argc, char* argv[]) renWin->SetSize(1024,1024); renWin->SetWindowName("ControlledFieldDensity"); renWin->Render(); iren->Initialize(); - // Slider - vtkSmartPointer sliderRep = vtkSmartPointer::New(); - sliderRep->SetMinimumValue(0.0); sliderRep->SetMaximumValue(10.0); sliderRep->SetValue(default_contour); - sliderRep->SetTitleText("Fraction RMS"); - sliderRep->GetTitleProperty()->SetColor( colors->GetColor3d("AliceBlue").GetData()); - sliderRep->GetLabelProperty()->SetColor( colors->GetColor3d("AliceBlue").GetData()); - sliderRep->GetSelectedProperty()->SetColor(colors->GetColor3d("DeepPink").GetData()); - sliderRep->GetTubeProperty()->SetColor( colors->GetColor3d("MistyRose").GetData()); - sliderRep->GetCapProperty()->SetColor( colors->GetColor3d("Yellow").GetData()); - sliderRep->SetSliderLength(0.05); sliderRep->SetSliderWidth(0.025); sliderRep->SetEndCapLength(0.02); - sliderRep->GetPoint1Coordinate()->SetCoordinateSystemToNormalizedDisplay(); sliderRep->GetPoint1Coordinate()->SetValue(0.1,0.1); - sliderRep->GetPoint2Coordinate()->SetCoordinateSystemToNormalizedDisplay(); sliderRep->GetPoint2Coordinate()->SetValue(0.9,0.1); - - vtkSmartPointer sliderWidget = vtkSmartPointer::New(); - sliderWidget->SetInteractor(iren); sliderWidget->SetRepresentation(sliderRep); - sliderWidget->SetAnimationModeToAnimate(); sliderWidget->EnabledOn(); - - vtkSmartPointer slidercallback = vtkSmartPointer::New(); - slidercallback->fu = fu; SliderCallback::contour = default_contour; - sliderWidget->AddObserver(vtkCommand::InteractionEvent, slidercallback); - // CommandHandler on fast poll timer vtkNew cmdHandler; cmdHandler->fu = fu; cmdHandler->camera = aCamera; cmdHandler->renderer = aRenderer; cmdHandler->iren = iren; - cmdHandler->sliderRep = sliderRep; + cmdHandler->captionActor = CaptionT; + cmdHandler->userCaptionActor = UserCaptionT; cmdHandler->isosurfaceLevel = default_contour; iren->AddObserver(vtkCommand::TimerEvent, cmdHandler); cmdHandler->pollTimerId = iren->CreateRepeatingTimer(100); diff --git a/visualisation/ForceAnalysis.cxx b/visualisation/ForceAnalysis.cxx new file mode 100644 index 00000000..e1111be4 --- /dev/null +++ b/visualisation/ForceAnalysis.cxx @@ -0,0 +1,633 @@ +// ForceAnalysis.cxx +// +// Reads a sequence of force snapshot files (LatticeComplexD, real part = force magnitude) +// and produces two outputs: +// +// 1. Tab-separated timeseries to stdout: +// idx Gauge_lat_rms Gauge_lat_hot Gauge_smr_rms ... +// where _rms is the lattice RMS and _hot is the value at --hotsite. +// +// 2. PNG images (one per force component per snapshot) rendered via VTK +// as isosurfaces of the force density, using the same pipeline as +// Visualise5D. Images are written to --pngdir/