Metadata-Version: 2.4
Name: pygx
Version: 0.5.4
Summary: PyGX: A library for manipulating Python objects.
Home-page: https://github.com/google/pygx
Author: PyGX Authors
Author-email: pygx-authors@google.com
License: Apache License 2.0
Keywords: ai machine learning automl mutable symbolic framework meta-programming
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Education
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Scientific/Engineering :: Human Machine Interfaces
Classifier: Topic :: Software Development :: Code Generators
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: docstring-parser>=0.12
Requires-Dist: pygx-core==0.5.3
Requires-Dist: termcolor>=1.1.0
Provides-Extra: all
Requires-Dist: docstring-parser>=0.12; extra == "all"
Requires-Dist: pygx-core==0.5.3; extra == "all"
Requires-Dist: termcolor>=1.1.0; extra == "all"
Requires-Dist: fsspec>=2023.3.0; extra == "all"
Requires-Dist: anyio>=4.0; extra == "all"
Requires-Dist: tqdm>=4.0; extra == "all"
Requires-Dist: cloudpickle>=3.0; extra == "all"
Requires-Dist: pyyaml>=6.0; extra == "all"
Provides-Extra: io
Requires-Dist: fsspec>=2023.3.0; extra == "io"
Provides-Extra: concurrent
Requires-Dist: anyio>=4.0; extra == "concurrent"
Requires-Dist: tqdm>=4.0; extra == "concurrent"
Provides-Extra: serialization
Requires-Dist: cloudpickle>=3.0; extra == "serialization"
Provides-Extra: yaml
Requires-Dist: pyyaml>=6.0; extra == "yaml"
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: keywords
Dynamic: license
Dynamic: license-file
Dynamic: provides-extra
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

<h1 align="center">
  <br>
  Meta-program Python Objects
</h1>

<p align="center">
  <a href="https://www.pygx.com">Website</a> ·
  <a href="https://docs.pygx.com">Documentation</a> ·
  <a href="https://docs.pygx.com/notebooks/intro/birdview/">Bird's-eye view</a> ·
  <a href="https://docs.pygx.com/tutorials/">Tutorials</a> ·
  <a href="https://docs.pygx.com/api/">API</a>
</p>

<p align="center">
  <a href="https://pypi.org/project/pygx/"><img alt="PyPI" src="https://img.shields.io/pypi/v/pygx.svg"></a>
  <img alt="Python" src="https://img.shields.io/pypi/pyversions/pygx.svg">
  <a href="LICENSE"><img alt="License" src="https://img.shields.io/pypi/l/pygx.svg"></a>
</p>

**Ordinary objects are built and then sealed — the call that produced them is
gone.** A `pg.Object` behaves like any Python object *and* keeps the structure
it was built from — so your code can generate, inspect, diff, patch and tune it
as easily as it runs it.

```python
import pygx as pg

class Model(pg.Object):
    units: int = 8

class Trainer(pg.Object):
    model: Model
    lr: float = 0.01

t = Trainer(model=Model(units=128))
t.sym_init_args                     # {'model': Model(units=128), 'lr': 0.01}

# Edit it anywhere in the tree — validated, and everything it touched re-initializes
t.sym_rebind({'model.units': 256})

# Replace any value with a space, and the class IS the search space
space = Trainer(model=Model(units=pg.oneof([64, 128])), lr=pg.oneof([0.1, 0.01]))
list(pg.iter(space))                # 4 programs: (64, 0.1) (64, 0.01) (128, 0.1) (128, 0.01)
```

No separate schema to keep in sync, no loop to rewrite when a parameter is added,
and no path-walking `getattr`/`setattr` to apply an override. That one
difference is the whole library — everything else is a consequence of it. The
idea has a name: **symbolic programming**, a paradigm where a program can
manipulate its own components as if they were plain data.

## Install

```bash
pip install pygx                # Python 3.12+
pip install "pygx[io]"          # fsspec-backed remote IO (GCS, S3, ...)
pip install "pygx[concurrent]"  # parallel execute/map with retries + progress
```

## More

<details>
<summary><b>Programs written by algorithms</b> — hand the space to a search</summary>

```python
algo = pg.algo.evolution.regularized_evolution(
    pg.algo.evolution.mutators.Uniform())

for net, feedback in pg.iter(space, 30, algo):
    feedback(score(net))     # your objective; the algorithm does the rest
```

Each `net` is a real, validated instance of your class — not a parameter vector
you have to decode. The algorithm mutates the *representation*, so the same
`Uniform()` mutator works on a neural architecture, a tour of cities, or a
symbolic expression, without being written for any of them.

Worked examples:
[OneMax](docs/notebooks/evolution/onemax.ipynb) ·
[Traveling Salesperson](docs/notebooks/evolution/tsp.ipynb) ·
[Function Regression](docs/notebooks/evolution/function_regression.ipynb)

</details>

<details>
<summary><b>Two programs, structurally compared</b> — <code>pg.diff</code></summary>

```python
pg.diff(baseline, candidate)
# Trainer(model=Model(units=Diff(left=8, right=16)))
```

Not a text diff of two dumps — a structural one that answers *which knob
differs*, at the position it differs. When runs are configs, this is the
question you actually ask.

</details>

<details>
<summary><b>Programs that aren't finished yet</b> — holes with names, not <code>None</code></summary>

```python
p = Trainer.partial()    # `model` has no default — so it's a hole
pg.is_partial(p)         # True
p.sym_missing()          # {'model': MISSING_VALUE}
```

An abstract object's `__init__` doesn't even run until it becomes concrete — so a
program can be assembled in stages by a user, a config file, a search, or a
model, and still be a real, checkable object at every stage.

</details>

<details>
<summary><b>Values that resolve from where they sit</b> — no threading through constructors</summary>

```python
class Layer(pg.Object, topo=True):
    dropout: Any = pg.symbolic.ValueFromParentChain()

Net(dropout=0.5, layer=Layer()).layer.dropout    # 0.5 — read from the ancestor
```

The alternative is threading the argument through every constructor in between,
or a global. Here the field is optional at construction and resolved at read
time from its position in the tree.

</details>

<details>
<summary><b>Code you don't own</b> — <code>pg.symbolize</code> and <code>pg.detour</code></summary>

```python
Sym = pg.symbolize(ThirdPartyClass)     # make it symbolic, no source edits

with pg.detour([(Adam, LAMB)]):         # change what a LIBRARY constructs
    third_party_training_loop()         # → returns a LAMB
```

`detour` redirects construction inside code you cannot edit — nested,
transitive, outer-scope-wins. The usual answers are monkeypatching or forking.

</details>

<details>
<summary><b>Provenance for generated programs</b> — where did this one come from?</summary>

```python
with pg.track_origin():
    variant = base.sym_clone()

variant.sym_origin.source is base       # True
variant.sym_origin.tag                  # 'clone'
```

When programs are produced by other programs — mutated, evolved, sampled — that
bookkeeping is otherwise yours to build.

</details>

<details>
<summary><b>Seeing it</b> — <code>pg.to_html</code> renders any value as a browsable tree</summary>

```python
pg.to_html(trainer)      # an interactive, collapsible tree
```

A deeply nested program is unreadable as `repr` output. Any symbolic value
renders as a browsable tree, in a notebook or a file.

</details>

> [**See it all on www.pygx.com**](https://www.pygx.com) — every capability with
> the problem it solves, side by side with the plain-Python alternative.

**Fast.** Hot paths run in a native Rust core (`pygx-core`, installed
automatically); the pure-Python implementation remains the executable
specification, and the full suite runs against both cores on every PR. Validated
construction beats pydantic v2 *with the whole symbolic model attached*, and
attribute reads are at dataclass parity — see the
[full report](docs/guide/perf.md).

**Portable.** Wheels for Linux, macOS and Windows on CPython 3.12–3.14, plus a
genuinely free-threaded 3.14t wheel (the GIL stays off; see
[`docs/design/gil-free.md`](docs/design/gil-free.md) §3). Elsewhere PyGX falls
back to the pure-Python core with identical behavior.

**And when not to reach for it.** If your objects are only ever *built and read*
— request payloads, plain records, a config loaded once and never manipulated —
a dataclass or pydantic model is the better tool. PyGX earns its keep the moment
a program becomes something you operate on.

## Documentation

| | |
| --- | --- |
| [**Bird's-eye view**](https://docs.pygx.com/notebooks/intro/birdview/) | 5–10 minute tour of the core ideas |
| [**Tutorials**](https://docs.pygx.com/tutorials/) | tracks for Python, ML, AutoML, and Evolution |
| [**Learning PyGX**](https://docs.pygx.com/learn/) | Symbolic OOP and Symbolic Detour, conceptually |
| [**API Reference**](https://docs.pygx.com/api/) | generated from source |
| [**Style guide**](docs/guide/style.md) | authoring `pg.Object` subclasses |

Runnable notebooks live in [`docs/notebooks/`](docs/notebooks/), source examples
in [`examples/`](examples/).

> Upgrading? See the [0.5 migration guide](docs/tutorials/general/migration_0_5.md).
> 0.5.2 removed three long-misspelled names without aliases; earlier versions
> are covered by the [0.4 guide](docs/tutorials/general/migration_0_4.md).

## Background

PyGX was originally built at **Google Brain / DeepMind** by Daiyi Peng to power
automated machine learning, under the name
[**PyGlove**](https://github.com/google/pyglove). The abstraction underneath —
symbolic object-oriented programming — turned out to be far more general than
AutoML. The original paper was
[published](https://proceedings.neurips.cc/paper/2020/file/012a91467f210472fab4e11359bbfef6-Paper.pdf)
at NeurIPS 2020, and the same ideas drive Google Cloud Vertex AI NAS, Pax, and
Vizier.

<details>
<summary>Citing PyGX</summary>

```bibtex
@inproceedings{peng2020pyglove,
  title={PyGlove: Symbolic programming for automated machine learning},
  author={Peng, Daiyi and Dong, Xuanyi and Real, Esteban and Tan, Mingxing and Lu, Yifeng and Bender, Gabriel and Liu, Hanxiao and Kraft, Adam and Liang, Chen and Le, Quoc},
  booktitle={Advances in Neural Information Processing Systems (NeurIPS)},
  volume={33},
  pages={96--108},
  year={2020}
}
```

</details>

## License

Apache License 2.0. PyGX is derived from PyGlove (also Apache 2.0); see
[`LICENSE`](LICENSE) and the per-file copyright headers for attribution.

PyGX is developed by Daiyi Peng.
