Skip to content

Cost and Factorization Analysis Utility - #577

Open
ajay-mk wants to merge 20 commits into
masterfrom
ajay/cost-analysis-utility
Open

Cost and Factorization Analysis Utility#577
ajay-mk wants to merge 20 commits into
masterfrom
ajay/cost-analysis-utility

Conversation

@ajay-mk

@ajay-mk ajay-mk commented Jul 9, 2026

Copy link
Copy Markdown
Member

Cost and Factorization Analysis Utility

Adds utilities/cost_analysis, a standalone tool that estimates the cost of evaluating a tensor equation without running it. It reads a serialized equation, optimizes and binarizes it, then writes a Markdown report on the largest intermediates, the most expensive contractions, peak storage, operation counts, and cache reuse.
Inspiration is from MPQC's SeQuantEngine class and the several knobs it exposes.

What it does

  • Input: a JSON driver pointing at one or more equation files (<head> = <rhs> in SeQuant serialization V1), plus context (spin basis, field, index-space sizes, factorization spaces).
  • Pipeline (per equation): deserialize → strip leading (anti)symmetrizer → flattenoptimize + binarize → catalog the tree's internal nodes → cache simulation across all results.
  • Output: per-result summary, Largest intermediates, Most expensive
    contractions
    , a Shape census by O/V/X signature, and a Cache table.

Usage

cost_analysis --driver examples/ccsd_r2.json

All driver knobs and their defaults are documented in the utility's README.md.

Tests

Two reference tests (ctest -R cost_analysis_) run the tool on examples/ and diff against frozen *.md.expected.

Not supported right now

  • CSV/PNO expressions: AsyCost is not proto-index aware; this utility should work once AsyCost is extended.

Example report:

EOM-CCSD(2h2p) R2 (53 terms; i=10, a=38)

Terms: 53; distinct intermediates: 94; reused: 9; largest: 8.3728 MB; peak storage: 40.1953 MB.

Total operations (symbolic): 4i^2a^4 + 18i^3a^3 + 26i^4a^2 + 2ia^4 + 30i^2a^3 + 104i^3a^2 + 26i^4a + 4ia^3 + 55i^2a^2 + 4i^3a + 10*i^2a

Largest intermediates

Rank Result Spaces Memory (MB) Uses Construction Local ops
1 I(a_2,a_1,i_2,a_3) VVOV 8.3728 1 ((g(i_3,i_4,a_3,a_4) * t(a_4,i_2)) * t(a_1,a_2,i_3,i_4)) 2*i^3a^3
2 I(a_2,a_1,i_2,a_3) VVOV 8.3728 1 (g(a_1,a_2,a_3,a_4) * t(a_4,i_2)) 2*ia^4
3 I(a_1,a_2,i_2,a_3) VVOV 8.3728 1 (g(i_3,a_1,a_3,a_4) * t(a_2,a_4,i_2,i_3)) 2*i^2a^4
4 I(a_2,a_1,i_2,a_3) VVOV 8.3728 1 (g(i_3,i_4,i_2,a_3) * t(a_1,a_2,i_3,i_4)) 2*i^3a^3
5 I(a_2,a_1,i_2,i_1) VVOO 2.2034 1 (((R(a_3,a_4,i_1,i_2) * g(i_3,i_4,a_3,a_4)) * t(a_1,i_3)) * t(a_2,i_4)) 2*i^3a^2
6 I(a_2,a_1,i_2,i_1) VVOO 2.2034 1 (((R(a_3,i_1) * g(i_3,i_4,i_2,a_3)) * t(a_1,i_3)) * t(a_2,i_4)) 2*i^3a^2
7 I(a_2,a_1,i_2,i_1) VVOO 2.2034 1 ((R(a_1,a_3,i_1,i_2) * (g(i_3,i_4,a_3,a_4) * t(a_4,i_4))) * t(a_2,i_3)) 2*i^3a^2
8 I(a_1,a_2,i_2,i_1) VVOO 2.2034 1 ((R(a_1,a_3,i_1,i_2) * f(i_3,a_3)) * t(a_2,i_3)) 2*i^3a^2
9 I(a_2,a_1,i_2,i_1) VVOO 2.2034 1 ((R(a_1,a_3,i_1,i_3) * (g(i_3,i_4,a_3,a_4) * t(a_4,i_2))) * t(a_2,i_4)) 2*i^3a^2
10 I(a_1,a_2,i_2,i_1) VVOO 2.2034 1 ((R(a_1,a_3,i_1,i_3) * g(i_3,i_4,i_2,a_3)) * t(a_2,i_4)) 2*i^3a^2
11 I(a_1,a_2,i_2,i_1) VVOO 2.2034 1 ((R(a_1,a_3,i_3,i_4) * g(i_3,i_4,a_3,a_4)) * t(a_2,a_4,i_1,i_2)) 2*i^2a^3
12 I(a_2,a_1,i_2,i_1) VVOO 2.2034 1 ((R(a_3,a_4,i_1,i_2) * g(i_3,i_4,a_3,a_4)) * t(a_1,a_2,i_3,i_4)) 2*i^4a^2
13 I(a_2,a_1,i_2,i_1) VVOO 2.2034 1 ((R(a_3,a_4,i_1,i_3) * g(i_3,i_4,a_3,a_4)) * t(a_1,a_2,i_2,i_4)) 2*i^3a^2
14 I(a_1,a_2,i_2,i_1) VVOO 2.2034 1 ((R(a_3,i_1) * (g(i_3,a_1,a_3,a_4) * t(a_4,i_2))) * t(a_2,i_3)) 2*i^3a^2
15 I(a_2,a_1,i_2,i_1) VVOO 2.2034 1 ((R(a_3,i_1) * (g(i_3,i_4,a_3,a_4) * t(a_2,a_4,i_2,i_4))) * t(a_1,i_3)) 2*i^3a^2
16 I(a_2,a_1,i_2,i_1) VVOO 2.2034 1 ((R(a_3,i_1) * (g(i_3,i_4,a_3,a_4) * t(a_4,i_3))) * t(a_1,a_2,i_2,i_4)) 2*i^3a^2
17 I(a_1,a_2,i_2,i_1) VVOO 2.2034 1 ((R(a_3,i_1) * g(i_3,a_1,i_2,a_3)) * t(a_2,i_3)) 2*i^3a^2
18 I(a_1,a_2,i_2,i_1) VVOO 2.2034 1 ((R(a_3,i_3) * g(i_3,a_1,a_3,a_4)) * t(a_2,a_4,i_1,i_2)) 2*i^2a^3
19 I(a_2,a_1,i_2,i_1) VVOO 2.2034 1 ((R(a_3,i_3) * g(i_3,i_4,i_1,a_3)) * t(a_1,a_2,i_2,i_4)) 2*i^3a^2
20 I(a_2,a_1,i_2,i_1) VVOO 2.2034 1 (R(a_1,a_2,i_1,i_3) * ((g(i_3,i_4,a_3,a_4) * t(a_3,i_4)) * t(a_4,i_2))) 2*i^3a^2

Most expensive contractions

Rank Result Spaces Uses Construction Operations
1 I(a_2,a_1,i_2,i_1) VVOO 1 (R(a_3,a_4,i_1,i_2) * g(a_1,a_2,a_3,a_4)) 2*i^2a^4
2 I(a_1,a_2,i_2,a_3) VVOV 1 (g(i_3,a_1,a_3,a_4) * t(a_2,a_4,i_2,i_3)) 2*i^2a^4
3 I(a_2,a_1,i_2,a_3) VVOV 1 ((g(i_3,i_4,a_3,a_4) * t(a_4,i_2)) * t(a_1,a_2,i_3,i_4)) 2*i^3a^3
4 I(a_2,a_1,i_1,i_2) VVOO 1 (R(a_1,a_3,i_1,i_3) * (g(i_3,a_2,a_3,a_4) * t(a_4,i_2))) 2*i^3a^3
5 I(a_1,a_2,i_2,i_1) VVOO 1 (R(a_1,a_3,i_1,i_3) * (g(i_3,i_4,a_3,a_4) * t(a_2,a_4,i_2,i_4))) 2*i^3a^3
6 I(a_2,a_1,i_2,i_1) VVOO 1 (R(a_1,a_3,i_1,i_3) * g(i_3,a_2,i_2,a_3)) 2*i^3a^3
7 I(i_3,a_1,i_2,i_1) OVOO 1 (R(a_3,a_4,i_1,i_2) * g(i_3,a_1,a_3,a_4)) 2*i^3a^3
8 I(i_3,a_2,i_2,i_1) OVOO 1 (g(i_3,a_2,a_3,a_4) * t(a_3,a_4,i_1,i_2)) 2*i^3a^3
9 I(i_3,a_2,i_2,a_3) OVOV 2 (g(i_3,i_4,a_3,a_4) * t(a_2,a_4,i_2,i_4)) 2*i^3a^3
10 I(a_2,a_1,i_2,a_3) VVOV 1 (g(i_3,i_4,i_2,a_3) * t(a_1,a_2,i_3,i_4)) 2*i^3a^3
11 I(a_2,a_1,i_2,a_3) VVOV 1 (g(a_1,a_2,a_3,a_4) * t(a_4,i_2)) 2*ia^4
12 I(a_2,a_1,i_2,i_1) VVOO 1 ((R(a_3,a_4,i_1,i_2) * g(i_3,i_4,a_3,a_4)) * t(a_1,a_2,i_3,i_4)) 2*i^4a^2
13 I(i_3,a_2,i_2,i_1) OVOO 1 ((g(i_3,i_4,a_3,a_4) * t(a_3,i_1)) * t(a_2,a_4,i_2,i_4)) 2*i^4a^2
14 I(a_2,a_1,i_2,i_1) VVOO 1 (R(a_1,a_2,i_3,i_4) * ((g(i_3,i_4,a_3,a_4) * t(a_3,i_1)) * t(a_4,i_2))) 2*i^4a^2
15 I(a_2,a_1,i_2,i_1) VVOO 1 (R(a_1,a_2,i_3,i_4) * (g(i_3,i_4,a_3,a_4) * t(a_3,a_4,i_1,i_2))) 2*i^4a^2
16 I(a_2,a_1,i_2,i_1) VVOO 1 (R(a_1,a_2,i_3,i_4) * g(i_3,i_4,i_1,i_2)) 2*i^4a^2
17 I(i_4,a_1,i_1,i_2) OVOO 1 (R(a_1,a_3,i_1,i_3) * (g(i_3,i_4,a_3,a_4) * t(a_4,i_2))) 2*i^4a^2
18 I(i_4,a_1,i_2,i_1) OVOO 1 (R(a_1,a_3,i_1,i_3) * g(i_3,i_4,i_2,a_3)) 2*i^4a^2
19 I(i_4,i_3,i_2,i_1) OOOO 2 (R(a_3,a_4,i_1,i_2) * g(i_3,i_4,a_3,a_4)) 2*i^4a^2
20 I(i_4,i_3,i_2,i_1) OOOO 2 (g(i_3,i_4,a_3,a_4) * t(a_3,a_4,i_1,i_2)) 2*i^4a^2

Shape census

Shape Size (MB) # Distinct Total uses
VVOV 8.3728 4 4
OVOV 2.2034 2 5
VVOO 2.2034 43 43
OOOV 0.5798 1 6
OVOO 0.5798 23 23
OOOO 0.1526 6 10
VV 0.0220 4 4
OV 0.0058 2 6
OO 0.0015 9 9

Cache (gated, volatile leaf = "R", min_repeats = 2)

Metric Value
Cached 30
Persistent 28
Persistent footprint (MB) 45.3697
Total cached footprint (MB) 45.5281

ajay-mk added 2 commits July 9, 2026 10:54
A standalone utility that estimates the cost of evaluating a serialized
tensor equation. Reads a JSON driver (space sizes + equation files), strips
the (anti)symmetrizer, flattens, then per summand optimizes and binarizes
once, reusing that tree for the intermediate catalog, cache simulation, and
--dump_tree, mirroring MPQC's SeQuantEngine. Emits a Markdown report on
largest intermediates, most expensive contractions, shape census, peak
storage, FLOPs, and a gated cache simulation. Includes a README and CCSD/DF
example drivers.
Diff generated reports against frozen *.md.expected via run_tests.cmake
(volatile lines stripped): a spin-orbital CCSD R2 (a Sum of terms, with a
--dump_tree check) and a single density-fitted product (the non-Sum path).
Comment thread utilities/cost_analysis/examples/df_r1.md.expected Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a new standalone cost_analysis utility under utilities/ that reads a JSON driver, optimizes/binarizes SeQuant equations, and emits a Markdown report (plus optional tree dumps) along with reference-style CTest coverage.

Changes:

  • Added the cost_analysis executable with JSON-driven configuration, equation parsing, optimization/binarization, cataloging, and optional cache simulation.
  • Added Markdown report generation and accompanying documentation/examples with frozen *.md.expected fixtures.
  • Integrated the tool into the build and added CTest-based golden-file regression tests.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
utilities/cost_analysis/run_tests.cmake CMake script to run the utility and compare generated reports to expected fixtures (with volatile lines stripped).
utilities/cost_analysis/report.hpp Declares report-writing and tree-string serialization helpers.
utilities/cost_analysis/report.cpp Implements Markdown report formatting, table generation, and tree stringification.
utilities/cost_analysis/README.md Documents the driver schema, workflow, report contents, and tests.
utilities/cost_analysis/examples/df_r1.md.expected Golden expected report output for DF R1 example.
utilities/cost_analysis/examples/df_r1.json JSON driver for DF R1 example.
utilities/cost_analysis/examples/df_r1.inp Serialized equation input for DF R1 example.
utilities/cost_analysis/examples/ccsd_r2.md.expected Golden expected report output for CCSD R2 example.
utilities/cost_analysis/examples/ccsd_r2.json JSON driver for CCSD R2 example (includes dump_tree).
utilities/cost_analysis/examples/ccsd_r2.inp Serialized equation input for CCSD R2 example.
utilities/cost_analysis/examples/.gitignore Ignores generated reports, filtered files, and dump-tree outputs.
utilities/cost_analysis/cost_analysis.hpp Defines shared config/result data structures between analysis and reporting.
utilities/cost_analysis/cost_analysis.cpp Implements the CLI entrypoint, JSON parsing, context setup, analysis pipeline, cache simulation, and report emission.
utilities/cost_analysis/CMakeLists.txt Adds build target and CTest registrations for the new utility.
utilities/CMakeLists.txt Wires utilities/cost_analysis into the utilities build.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread utilities/cost_analysis/report.cpp Outdated
Comment thread utilities/cost_analysis/CMakeLists.txt Outdated
Comment thread utilities/cost_analysis/run_tests.cmake Outdated
Comment thread utilities/cost_analysis/run_tests.cmake Outdated
Comment thread utilities/cost_analysis/run_tests.cmake Outdated
Comment thread utilities/cost_analysis/cost_analysis.cpp Outdated
ajay-mk added 8 commits July 9, 2026 13:48
The generated timestamp changed every run; only the volatile-line stripping
in run_tests.cmake kept the reference tests deterministic. Remove it (and the
now-unused <chrono> include). Keep the SeQuant version/hash line, still
stripped before diffing. Reference fixtures now carry a <version> placeholder
instead of a frozen timestamp+hash.
@ajay-mk
ajay-mk marked this pull request as ready for review July 10, 2026 21:10
ajay-mk added 4 commits July 13, 2026 19:56
The input list is the equations to analyze; 'results' collided with the
tool's computed output. Rename the JSON key, EquationSpec, and Config field
so input equations read distinctly from output results.
Mirrors the existing min_repeats guard: a negative top_n wraps to a huge
size_t, and a negative max_footprint silently disables the cache budget.
The driver chdir's into its own parent, so a relative output.path resolves
there rather than the invocation directory; print the absolute path in the
confirmation line and document the resolution rule.

Also document the registry-default fallback for unlisted sizes, correct the
report layout description (Cache is one pooled section, not per-result), and
trim the CSV/PNO note.
@ajay-mk
ajay-mk requested review from Krzmbrzl and evaleev July 14, 2026 13:52

@Krzmbrzl Krzmbrzl left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm wondering whether there is potential for merging with the external interface. I am currently in the process of rewriting its core design to be processing-step oriented and in this design creating this cost analysis could be a useful thing to be capable of.

Plus, this would have the advantage of a unified JSON driver input syntax.

That's something for the future though and shouldn't hold up this PR.

Comment thread utilities/cost_analysis/CMakeLists.txt Outdated
@ajay-mk

ajay-mk commented Jul 15, 2026

Copy link
Copy Markdown
Member Author

I'm wondering whether there is potential for merging with the external interface. I am currently in the process of rewriting its core design to be processing-step oriented and in this design creating this cost analysis could be a useful thing to be capable of.

I think that is a good idea. I initially considered doing that, but wasn't sure if we would want to merge this utility into SeQuant. I wrote it mainly to see how factorization changes when we apply DF/THC to the equations, but turned out to be quite useful.

@Krzmbrzl

Copy link
Copy Markdown
Collaborator

Yeah, I think it wouldn't really fit with the current structure of the external interface but with the refactor it should be relatively easy to pull something like this off 👍

Replace the run_tests.cmake -P wrapper with a generate, sed (strip
volatile line), diff multistep process.
Comment thread utilities/cost_analysis/CMakeLists.txt Outdated
Add --omit-revision to drop the volatile git-revision line, and end the
report with a single trailing newline (leading section separators instead
of trailing ones) so it is byte-stable. The tests then use plain
cmake -E compare_files instead of sed/diff, making them portable. The tree
dump is diffed against a new R2.tree.txt.expected fixture.
@evaleev evaleev added this to the 2.3 milestone Jul 27, 2026
@ajay-mk ajay-mk added the feature New feature label Aug 3, 2026
@ajay-mk

ajay-mk commented Aug 4, 2026

Copy link
Copy Markdown
Member Author

Another round of Claude review found a few issues, fixing now fixed.

ajay-mk added 4 commits August 4, 2026 00:51
spbasis was a bare string comparison, so any typo silently selected
spinfree with no diagnostic; validate it like the sibling field option.
volatile_weight had no validation at all -- a non-positive value makes
the DP tie-break's cost negative for volatile-containing subsets, which
the optimizer would then treat as cheapest, silently choosing the wrong
schedule. Reject it, matching MPQC's own SeQuantEngine validation of the
same option.
res.largest_mem and res.peak_storage compared via AsyCost::operator<,
which orders by symbolic polynomial degree, not actual size at the
driver's configured sizes. A lower-degree intermediate can be numerically
larger than a higher-degree one at those sizes, so the reported
largest/peak could silently be the wrong term -- while the tables printed
right below them already sort by actual MB. Compare by .ops() instead.

The library's own min_storage() has the same symbolic-max issue
internally (it's used for peak_storage), so add a local numeric_peak_storage
that reimplements its traversal with a numeric max; fixing min_storage()
itself is out of scope here since it's shared library code.

Updates the df_r1 fixture: its peak storage was under-reported at 6.8420 MB
by the old comparison; the correct value is 7.0801 MB.
Only the ofstream open was checked; std::ofstream::operator<< doesn't
throw on write failure by default, so a disk-full or I/O error mid-write
would silently produce a truncated report with exit code 0. Close the
stream and check it before declaring success.
- setup_context's summary comment said field is applied per-space; the
  code six lines below applies it to every registered space.
- space_signature's 'X' bucket comment said size-grouping in the shape
  census only happens with multiple aux spaces registered; X also catches
  any other non-hole/non-particle space (e.g. frozen-core), so grouping
  can happen with one aux space or none.
- report_expensive's sort comment had an unfinished trailing fragment.
- README didn't document that machine_balance/fast_mem_elems are only
  forwarded when volatile_leaf is set and cache.enabled is true, or that
  toggling cache.enabled can change the chosen contraction order.
- README described Cached/Persistent as separate counts; Persistent is a
  subset of Cached, not disjoint from it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants