---
title: "Optimizing paths with Heavy"
description: "Call Heavy, read contraction paths, objective values, and timings, and save the resulting plan."
eyebrow: "Tutorials"
---

First prepare the example network structure, dimensions, and numerical arrays from [Tensor-network input in Quick start](/docs/quick-start#network).

## Running Heavy and keeping its report {#run}

After installing the full wheel containing Light / Heavy, reuse the network in [Quick start](/docs/quick-start#network), or supply your own `inputs`, `output`, and `size_dict`.

Save the network definition and the call below as `heavy_example.py`, and set the thread count when launching it:

```bash
RAYON_NUM_THREADS=24 python heavy_example.py
```

### Obtaining the result {#levels}

`arctn_schedule` optimizes the contraction order without executing numerical arrays. Its `report` is a Python dictionary containing the path, metrics, and optional slicing information.

```python
from arctn import arctn_schedule

report = arctn_schedule(
    inputs, output, size_dict,
    preset="heavy", seed=17,
    use_ssa=True,
)

path = report["path"]
print(report["log10_flops"])
print(report["log2_read_write"])
print(report["planner_objective_score_log2"])
```

Heavy uses a larger search budget to seek better paths. It is still heuristic: it returns the best completed candidate from the current run, with no guarantee of local or global optimality or of outperforming Light every time.

## Reading the result {#read}

The default objective is $F+64R$. When comparing searches on the same network with the same weights, read `planner_objective_score_log2`; FLOPs alone do not capture this weighted objective.

### Path and objective fields {#path-fields}

| Field | Meaning |
| --- | --- |
| `path`, `path_format` | Final path and encoding; defaults to `linear-v1`, while the example uses `ssa-v1` |
| `planner_objective` | Optimization objective name |
| `flops_weight`, `read_write_weight` | Objective weights for this call |
| `planner_objective_score_log2` | Base-2 logarithm of the final objective, including all slices when sliced |
| `log10_flops`, `log2_read_write` | Work and read/write volume of the final path on the **unsliced network**, in base-10 and base-2 logarithms respectively |
| `log2_max_size` | Base-2 logarithm of the largest binary contraction result element count on the unsliced path |
| `log2_max_contraction_size` | Base-2 logarithm of the largest sum of logical input and result elements in one unsliced-path step |
| `log2_total_size`, `log2_peak_size` | Base-2 logarithms of total writes and peak live elements on the unsliced path |
| `wall_s` | Search time in seconds recorded by the Light / Heavy compiled library; excludes subsequent array execution |

Integers in `path` are tensor IDs interpreted according to `path_format`; integers in `sliced_legs` are internal index IDs. See [Contraction trees and paths](/docs/tensor-network#what-a-path-decides) for the two path formats.

Path metrics are computed from network structure and dimensions, not measured execution time or process memory. See [Path metrics and optimization objective](/docs/path-metrics) for the formulas.

### Slicing fields {#slicing-fields}

The example does not set `target_size`. To limit per-slice intermediates, configure slicing as in [Controlling intermediates](/docs/tutorial-slicing#fixed), then read these fields:

| Field | Meaning |
| --- | --- |
| `sliced_legs` | Internal integer IDs of sliced indices |
| `log2_n_slices` | Base-2 logarithm of the slice count |
| `sliced_log10_flops_total` | Base-10 logarithm of total FLOPs across all slices |
| `sliced_log2_max_size` | Base-2 logarithm of the largest binary contraction result element count per slice |
| `planner_log2_read_write` | Base-2 logarithm of total read/write volume across all slices |
| `target_size` | Requested maximum result element count per slice |
| `memory_constraint_metric` | Definition of the size constraint |
| `max_intermediate_log2_elements_per_slice` | Base-2 logarithm of the final largest result element count per slice |

`sliced_legs` lists which indices are sliced; its length is not the slice count. One index of dimension 4, for example, produces 4 slices and `log2_n_slices` equal to 2.

Without `target_size`, `sliced_legs` is empty, `log2_n_slices` is 0, some `sliced_*` fields are absent, and `max_intermediate_log2_elements_per_slice` is `None`. Even with `target_size`, slicing may not be needed; `bool(report["sliced_legs"])` indicates whether any indices were actually selected.

Check the size constraint using the **per-slice** size, not the unsliced `log2_max_size`. Compare total work with `sliced_log10_flops_total` and `planner_log2_read_write`; `planner_objective_score_log2` already includes the objective across all slices using the current weights.

## Setting threads and reading timings {#repeats}

The example calls Heavy once. `RAYON_NUM_THREADS=24` gives 3 independent search starts sharing 24 worker threads; no outer seed loop is needed. `seed=17` sets the base seed, and the library derives the others. This setting does not reserve 24 physical cores. See [Recommended thread configurations](/docs/parallelism#recommended-threads) for 8, 16, and 32 threads.

Read the search time:

```python
print("Search time (seconds):", report["wall_s"])
```

Internal candidate lists, stage records, and independent-start reports are not public return fields. To time the entire Python call, use `time.perf_counter()` around it; that covers a different interval from the search time recorded by the compiled library.

## Saving the contraction plan {#artifacts}

Given `report`, use `ArcTNExecutionPlan.from_schedule` to save the network, SSA path, and slicing information without searching again:

```python
from arctn import ArcTNExecutionPlan

plan = ArcTNExecutionPlan.from_schedule(
    report, inputs=inputs, output=output, size_dict=size_dict,
)
plan.save("plan.json")
```

### Subsequent execution and reuse {#paths}

See [Saving and reusing paths](/docs/tutorial-reuse#save-load) for loading files, executing contractions, and updating arrays. To pass paths to other tools, see [Path representations and persistence](/docs/execution-plan#representations).
