Changelog
The package is before its first stable release, so a removed name carries no deprecation and no alias. Each removal below names what replaces it.
A removal also lands in every package that bridges to AnnNet, and none of them
is in this test suite. DEPENDENTS.md says who those packages are and what to
do about it, and tests/test_dependents.py fails the build when a name one of
them calls goes away.
Unreleased
Changed, and a caller can see it
- A column read gives back a read-only array.
G.N["score"]andG.E["weight"]now hand back a window onto the storage rather than a copy of it, which is what makes the read cost what slicing an array costs. A write through that window would reach the graph with no validation, no clock bump and no history entry, so it is refused:
column = G.N['score']
column.sum() # works, as before
column * 2 # works, as before — the result is a new array
column[0] = 1.0 # ValueError: assignment destination is read-only
To change values, copy first — G.N["score"].copy() is your own array — or
write through the entry points that already existed, G.N["score"] = values
and G.attrs.set_node_attrs. The rule holds on every read path, so a caller
never has to ask which one answered.
-
A column is good until the next write to the graph. After a write, a column you are still holding is stale, and what it shows then is not something the package promises.
.copy()is the documented way to hold values across a change. Code that reads and uses a column in one expression — which is nearly all code — never reaches that boundary. -
The native format carries a direction policy. A graph whose edges declare a flexible-direction policy used to lose it on a round trip through
.annnet, although cx2 kept it. It now survives. A file written before this change reads as before.
Removed
-
GraphView.X, which was the incidence matrix under the name the graph itself dropped. A view spells its matrices the way the graph does, so it isview.B. -
annnet.from_omnipathandannnet.io.from_omnipath. Access to one knowledge base belongs in the client for that knowledge base, which is what returns AnnNet objects. The replacement isomnipath_client.to_annnet, which builds a graph from any OmniPath table, andomnipath_client.annotate_nodes, which gives every node of that graph what OmniPath knows about it.omnipath_client.relations(as_graph=True)fetches and builds in one call. The package now declares no HTTP client and downloads nothing. - Every map from an id to a position:
entity_to_idx,idx_to_entity,edge_to_idx,idx_to_edgeandentity_types. A position belongs to one materialized matrix.G.idxtranslates a coordinate a caller already holds, andG.views.entity_kinds()reads the kind of each entity. - Every position in a lookup.
get_edgetakes an id and raises on a column.get_nodetakes an id too, and gives back aNodeView. The n-th node of a sequence isG.N[n]. G.X(), which was a second name forG.S, the signed coefficient incidence. The named matrices areG.A,G.B,G.H,G.SandG.L.- The count aliases:
num_vertices,num_edges,num_supra_vertices,number_of_vertices,number_of_edgesand the threeglobal_*_countwrappers. Usencount()andecount(), with the supra-node count an option of the first.nv,neandnv_suprastay as the property spelling. G.vertex_attributesandG.edge_attributes, which were the storage of the graph under a public name.G.obsandG.varbuild a table for the caller, and writing into one changes nothing the graph holds.
Changed
- The generic attributes of a node and of an edge live in slot-indexed columns. One write lands in one cell and builds no table, at any size, and reading one attribute of every element is a slice of the array the store holds.
- Set algebra between two graphs:
|,&,-,^,|=. It applies to the node set and the edge set together, and an edge survives only when every node it names does. - Each contextual attribute level has one entry point, named for the level:
G.slices.attrs,G.attrs.edge_slice,G.layers.attrs,G.layers.node_attrs,G.layers.aspect_attrsandG.layers.elementary_attrs. - The PyTorch Geometric writer moved from
annnet.adapters.pyg_adaptertoannnet.io.pyg, with no alias at the old path.
Renamed
- The package says "node", everywhere and only.
vertexis gone from every method, parameter, attribute, column name and document:add_verticesisadd_nodes,remove_verticesisremove_nodes,vertices()isnodes(),has_vertexishas_node,supra_verticesissupra_nodes, andvertex_idisnode_idin every table the package hands back. Two words for one concept was the largest of the faults this release fixes, not a reason to keep it.nv,neandnv_supranever carried the word and do not move. - The native format writes the new words. Its reader takes both, so an archive written before this release still loads: four member names, two columns and the entity kind each map the old spelling forward.
Added
- The eight attribute tables, under one namespace and one convention. They
carried three spellings —
G.obsandG.varfor the two generic axes,G.slice_attributesand two siblings for three of the contextual levels, andG.contextual_table(level)for all six. Same concept, three ways to reach it, and the read side in a different namespace from the setter that writes it. They areG.attrs.<address>now, beside those setters:
G.attrs.nodes # G.obs
G.attrs.edges # G.var
G.attrs.slices
G.attrs.aspects
G.attrs.layers # one label per aspect, the whole coordinate
G.attrs.edge_slices
G.attrs.node_layers
G.attrs.elementary_layers # one label inside one aspect
Every older spelling still answers, and obs and var keep the anndata
parallel, so nothing has to move.
-
G.attrs.backend, which every table follows, andG.attrs.table(name, backend=...)for the workflow that genuinely mixes two. The backend picks the container and never the content. -
G.NandG.E, the node sequence and the edge sequence. A string key is an attribute column, an integer key is a position in that sequence, andselectandfindfilter it. G.get_node(node_id), which gives aNodeView: the id, the kind of the node, the layers it lives in, and its attributes.
Fixed
-
A whole table assigned to the graph is visible to the next read. Assigning
G.slice_attributes,G.edge_slice_attributesorG.layer_attributeswrote the store but left the materialized table where it was, so the next read answered with the values the assignment had replaced — without the rows it added, and with nothing to say so. Reading a table before assigning one was enough to hit it, which is what a round trip through an adapter does. -
G.attrs.table(name, backend=...)keeps the columns of a table with no rows. It went through rows, and rows carry no schema, so an empty table came back with no columns at all — including the column it is addressed by. -
Asking for the backend a table already has costs nothing. The name passed in was compared against the table without being resolved first, so
"auto"never matched a concrete backend and rebuilt the whole table. -
A layer column is typed the same whether or not the table holds a row. A layer coordinate is a tuple, so the column holding it is a list of strings.
G.attrs.layersandG.attrs.node_layersdeclared it text, so an empty table and a filled one disagreed about the type of the column they are keyed by. -
A write to one contextual level no longer rebuilds the tables of the other five. They shared one clock, so annotating a slice aged the node-layer table as well. Each level keeps its own now.