You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
BREAKING (within v3.0.0 — alias support is new in this PR): rename the alias
relationship attribute on GdxSymbol from `aliased_with` to `alias_of`. The
old name was a gdxpds invention; gams.transfer uses `alias_with` (active
voice; not `aliased_with`) and gdxcc has no Python-attribute analog. `alias_of`
reads naturally ("at is `alias_of` t"), matches the active voice of the
gams.transfer naming, and isn't confused with the unrelated "aliased with"
phrasing that suggested help-from a parent. Renames propagated:
`GdxSymbol.alias_of` / `alias_of_name`, `resolve_alias_of()`, the
`alias_of=` constructor kwarg, and the corresponding private attrs.
Also adds a comment in `_gdxcc_engine.GdxccEngine.write_symbol` cross-
referencing the gams.transfer engine's `gt.Alias` / `gt.UniverseAlias`
dispatch: `gdxAddAlias` accepts `"*"` as a parent without any special-case
call, so the gdxcc engine has a single uniform write path for both
named-Set aliases and universe aliases.
Test matrix on this branch: PASS / PASS / PASS / OK across
.venv-gams-{34,49,51} and .venv-no-gams. Ruff and pyright clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: CLAUDE.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -97,7 +97,7 @@ Things that aren't obvious from one file:
97
97
-**Symbol kinds drive column shape.**`GamsDataType` ([src/gdxpds/gdx.py](src/gdxpds/gdx.py)) — Set, Parameter, Variable, Equation, Alias. Variables and Equations get five value columns (Level, Marginal, Lower, Upper, Scale); Parameters and Sets get a single `Value` column. Write code in [src/gdxpds/write_gdx.py](src/gdxpds/write_gdx.py) infers the type from DataFrame shape and naming.
98
98
-**Special values.** GAMS encodes NA/EPS/+Inf/-Inf/UNDEF as fixed magic floats (e.g. 1E300, 2E300, 3E300). [src/gdxpds/special.py](src/gdxpds/special.py) converts these to/from numpy equivalents (`np.nan`, `np.inf`, and `None` for UNDEF) on read/write. Parameters get this conversion; Sets/Aliases do not (their value column is text, see below). UNDEF (`None`) is preserved on write by both engines; the gams.transfer write passes `eps_to_zero=False` so EPS survives too.
99
99
-**Set value = element text; membership = row presence.** A Set/Alias has one `Value` column holding the GAMS element text (a string; `""` = a member with no text). Every row is a member — there is no boolean. `_fixup_set_value` ([src/gdxpds/gdx.py](src/gdxpds/gdx.py)) normalizes the column to text on assignment (a `bool`/`c_bool`/missing value → `""`), so a Set can be built from dims alone, from booleans, or from text. The read path always fetches text (`gdxGetElemText()` on gdxcc; the records frame on gams.transfer); there is no `load_set_text` flag.
100
-
- **Aliases.** An Alias reads like the Set it aliases and records the parent in `GdxSymbol.aliased_with` (a parent ref, or `None`). It is written by both engines (`gdxAddAlias` / `gt.Alias`); the parent must precede it (no relaxed fallback — `DomainError` otherwise). `to_gdx(aliases={alias: parent})` and `gdxpds.gdx.append_alias()` build them; ordering follows `reorder_for_strict_domains()`, which now adds the alias→parent edge. The parent is typically a Set, but an Alias is accepted too: GDX supports chained aliases, and the gdxcc engine preserves the chain on disk (`aat -> at -> t`) while gams.transfer flattens it to the root (`aat -> t`). On read both engines produce a same-file `aliased_with` ref. *Universe* aliases (alias of `*`) are a documented edge: they read without error (`aliased_with` resolves to `universal_set`) and round-trip within one engine, but the engines disagree on membership (gdxcc includes the `*` element, gams.transfer doesn't), so `universe_alias_fixture.gdx` is excluded from the cross-engine parity glob and covered by `tests/test_alias.py`.
100
+
- **Aliases.** An Alias reads like the Set it aliases and records the parent in `GdxSymbol.alias_of` (a parent ref, or `None`). It is written by both engines (`gdxAddAlias` / `gt.Alias`); the parent must precede it (no relaxed fallback — `DomainError` otherwise). `to_gdx(aliases={alias: parent})` and `gdxpds.gdx.append_alias()` build them; ordering follows `reorder_for_strict_domains()`, which now adds the alias→parent edge. The parent is typically a Set, but an Alias is accepted too: GDX supports chained aliases, and the gdxcc engine preserves the chain on disk (`aat -> at -> t`) while gams.transfer flattens it to the root (`aat -> t`). On read both engines produce a same-file `alias_of` ref. *Universe* aliases (alias of `*`) are a documented edge: they read without error (`alias_of` resolves to `universal_set`) and round-trip within one engine, but the engines disagree on membership (gdxcc includes the `*` element, gams.transfer doesn't), so `universe_alias_fixture.gdx` is excluded from the cross-engine parity glob and covered by `tests/test_alias.py`.
101
101
-**Lazy + idempotent GAMS bind.**`load_gdxcc()` in [src/gdxpds/tools.py](src/gdxpds/tools.py) binds the GAMS library and populates `gdxpds.special` dicts on the first GDX op (called by the gdxcc engine's `__init__` before it creates a handle, and by `info()` inside try/except for diagnostics). Process state: `tools._bindings_source`, `tools._loaded_gams_dir`.
102
102
-**`gams_dir=` on the first GDX op selects the bound install.** Once loaded, subsequent `gdxCreateD(H, <dir>, ...)` calls are no-ops against the bound library regardless of `<dir>`; `load_gdxcc()` warns when a caller passes a `gams_dir` that differs from `_loaded_gams_dir`. One GAMS library per process — multi-version testing is one-venv-per-GAMS. In-process swap is feasible via `gdxLibraryUnload()` but unimplemented (design notes tracked in a GitHub issue).
103
103
-**GDX handle lifecycle** (SWIG-bound `gdxcc`; gdxcc engine only — the gams.transfer engine holds no handle). The full `new_gdxHandle_tp` → `gdxCreateD` → `gdxFree` → `delete_gdxHandle_tp` sequence lives in one place: the `_GdxHandle` RAII class in [src/gdxpds/tools.py](src/gdxpds/tools.py), used by all three create sites (`load_gdxcc` and `load_specials` via `with`; `GdxccEngine.__init__` keeps the instance). It encodes two SWIG hazards so callers don't have to:
Copy file name to clipboardExpand all lines: doc/source/overview.md
+5-5Lines changed: 5 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -293,7 +293,7 @@ If you prefer the simpler API that works with dicts of DataFrames, see [the `dom
293
293
GAMS lets one Set be an *alias* of another — `alias(t, at)` makes `at` another name for set `t`. `gdxpds` surfaces the relationship on `GdxSymbol`:
294
294
295
295
-`GdxSymbol.data_type` is {py:attr}`gdxpds.gdx.GamsDataType.Alias`, and the symbol reads like the Set it aliases (same elements, same element text).
296
-
-`GdxSymbol.aliased_with` is the parent Set as a `GdxSymbol` reference (or `None` for non-aliases). Unlike a relaxed domain, an alias has no fallback: its parent must exist in the file when it is written, or the write raises {py:class}`gdxpds.DomainError`.
296
+
-`GdxSymbol.alias_of` is the parent Set as a `GdxSymbol` reference (or `None` for non-aliases). Unlike a relaxed domain, an alias has no fallback: its parent must exist in the file when it is written, or the write raises {py:class}`gdxpds.DomainError`.
297
297
298
298
**Viewing on read:**
299
299
@@ -303,10 +303,10 @@ with gdxpds.gdx.GdxFile(lazy_load=False) as gdx:
303
303
gdx.read('data.gdx')
304
304
at = gdx['at']
305
305
print(at.data_type) # GamsDataType.Alias
306
-
print(at.aliased_withis gdx['t']) # True — points at the parent Set
306
+
print(at.alias_ofis gdx['t']) # True — points at the parent Set
307
307
```
308
308
309
-
**Setting on write.** Build the parent Set, then the alias — via {py:func}`gdxpds.gdx.append_alias` or a `GdxSymbol` with `aliased_with`:
309
+
**Setting on write.** Build the parent Set, then the alias — via {py:func}`gdxpds.gdx.append_alias` or a `GdxSymbol` with `alias_of`:
Aliases of a *named Set* (the common case) are fully supported on both engines. A **universe alias** — an alias of the universe set `*` (`aliased_with` resolves to the file's `universal_set`) — reads without error and round-trips within a single engine, but the engines disagree on its membership (`gdxcc` includes the `*` element, `gams.transfer` does not), so it is not cross-engine identical.
330
+
Aliases of a *named Set* (the common case) are fully supported on both engines. A **universe alias** — an alias of the universe set `*` (`alias_of` resolves to the file's `universal_set`) — reads without error and round-trips within a single engine, but the engines disagree on its membership (`gdxcc` includes the `*` element, `gams.transfer` does not), so it is not cross-engine identical.
331
331
332
-
**Chained aliases (alias of an alias)** are also supported: GDX itself permits a chain (`aat -> at -> t`), and both engines accept it on write and resolve `aliased_with` to a same-file symbol on read. The two engines differ on what reaches disk: the `gdxcc` engine preserves the chain (`aat -> at`), while `gams_transfer` flattens to the root (`aat -> t`). Either form reads back identically through `gdxpds`.
332
+
**Chained aliases (alias of an alias)** are also supported: GDX itself permits a chain (`aat -> at -> t`), and both engines accept it on write and resolve `alias_of` to a same-file symbol on read. The two engines differ on what reaches disk: the `gdxcc` engine preserves the chain (`aat -> at`), while `gams_transfer` flattens to the root (`aat -> t`). Either form reads back identically through `gdxpds`.
0 commit comments