Metadata-Version: 2.4
Name: cimhub_raw
Version: 2.0.0a3
Summary: CIMHub PSS/E Library
Author-email: Andrew R Fisher <andrew.fisher@pnnl.gov>
Project-URL: Homepage, https://github.com/PNNL-CIM-Tools/CIMHub_2_0
Project-URL: Bug Tracker, https://github.com/PNNL-CIM-Tools/CIMHub_2_0
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Classifier: Environment :: Console
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: cimhub_core<2.1,>=2.0.0a2
Requires-Dist: networkx

# cimhub_raw

# cimhub_raw

RAW v34 ↔ CIM converter for the cimhub ecosystem.

Supports reading, writing, and bidirectional conversion between RAW v34 files and CIM (IEC 61970) network models using [cimgraph](https://github.com/PNNL-CIM-Tools/cimgraph).

---

## Installation

```bash
uv sync
# or
pip install -e .
```

Set the CIM profile before importing (required by cimgraph):

```python
import os
os.environ.setdefault("CIMG_CIM_PROFILE", "cim17v40")
```

---

## Usage

### 1. Read a RAW file (no CIM conversion)

The source is bound at construction and `read_all()` returns a
`cimhub_core.GraphModel` — the same `graph[RecordClass][identifier]` container
used across the cimhub ecosystem. Records are the LinkML-generated dataclasses
in `raw34_schema`.

```python
from cimhub_raw.importer.raw34_reader import Raw34Reader
import cimhub_raw.schema.raw_v34.raw34_schema as raw

raw_network = Raw34Reader("network.raw").read_all()

# Counts and iteration go through the GraphModel API:
print(f"Buses: {raw_network.count(raw.BusRecord)}")
print(f"Loads: {raw_network.count(raw.LoadRecord)}")
print(f"Generators: {raw_network.count(raw.GeneratorRecord)}")

for bus in raw_network.list_by_class(raw.BusRecord):
    print(f"  Bus {bus.bus_number}: {bus.name}, {bus.base_voltage} kV")
```

> **Note:** the reader used to return a `RawModel` container with
> `.bus_records` / `.load_records` list attributes. That container is gone —
> use `count()` / `list_by_class()` / `find_by_attribute()` instead.

### 1a. Basic editing style

Records live at `raw_network.graph[RecordClass][identifier]`. The identifier is
the composite key the reader assigns per record type (see the table below), so
you can look a record up directly and mutate its fields in place:

```python
import cimhub_raw.schema.raw_v34.raw34_schema as raw

# Direct access by composite identifier — generator id "3" on bus 10111:
gen = raw_network.graph[raw.GeneratorRecord]['10111_3']
gen.p_output = 250.0          # set active power (MW)
gen.q_output = 40.0           # set reactive power (MVAr)

# Or find a record without knowing its exact key:
(bus,) = raw_network.find_by_attribute(raw.BusRecord, 'bus_number', 10111)
bus.base_voltage = 138.0

# find_by_attribute returns a LIST (exact string match), so index or unpack it:
loads = raw_network.find_by_attribute(raw.LoadRecord, 'bus_number', 10111)
for load in loads:
    load.status = 0           # take every load on the bus out of service
```

**Composite identifier keys** (what to put in `graph[Cls][...]`):

| Record | Identifier key |
|---|---|
| `BusRecord` | `bus_number` |
| `LoadRecord` | `bus_number_loadid` |
| `FixedShuntRecord` | `bus_number_shuntid` |
| `GeneratorRecord` | `bus_number_genid` |
| `BranchRecord` / `SwitchRecord` | `from_to_ckt` |
| `TransformerRecord` | `i_j_k_ckt` |
| `SwitchedShuntRecord` | `bus_number` |
| `VscDcRecord` | `name_ibus` |

If you don't know the exact key, `find_by_attribute(cls, attr, value)` is the
safe path — it matches on the string form of any field.

### 2. Write a RAW file

The writer's standard input is a `GraphModel` — pass the one you read/edited
straight to `write()`:

```python
from cimhub_raw.exporter.raw34_writer import Raw34Writer

writer = Raw34Writer()
writer.write(raw_network, "output.raw")
```

To render a single record without writing a file, use `writer.serialize(obj)`.

### 3. RAW → RAW round-trip (read + write, no CIM)

```python
from cimhub_raw.importer.raw34_reader import Raw34Reader
from cimhub_raw.exporter.raw34_writer import Raw34Writer

raw_network = Raw34Reader("network.raw").read_all()
Raw34Writer().write(raw_network, "output.raw")
```

### 4. RAW → CIM

Convert a RAW file to a CIM `NodeBreakerModel` (cimgraph):

```python
import os
os.environ.setdefault("CIMG_CIM_PROFILE", "cim17v40")

from cimhub_raw.importer.raw34_to_cim import raw34_to_cim
import cimhub_raw.schema.data_profile as cim

network = raw34_to_cim("network.raw")

buses = network.list_by_class(cim.TopologicalNode)
loads = network.list_by_class(cim.EnergyConsumer)
gens  = network.list_by_class(cim.SynchronousMachine)

print(f"Buses: {len(buses)}, Loads: {len(loads)}, Generators: {len(gens)}")
```

Pass `include_extensions=True` to attach raw_extension objects (e.g. `RawBusRecord`)
alongside each CIM object — useful for lossless round-trips:

```python
network = raw34_to_cim("network.raw", include_extensions=True)
```

### 5. CIM → RAW

Export a CIM `BusBranchModel` back to a RAW file:

```python
from cimhub_raw.exporter.cim_to_raw import cim_to_raw

cim_to_raw(network, "output.raw")
```

Or build the intermediate `GraphModel` first (e.g. to inspect before writing):

```python
from cimhub_raw.exporter.cim_to_raw import Raw34Exporter
from cimhub_raw.exporter.raw34_writer import Raw34Writer

raw_model = Raw34Exporter().to_model(network, system_base_mva=100.0)
writer = Raw34Writer()
writer.write(raw_model, "output.raw")
```

### 6. Full RAW → CIM → RAW round-trip

```python
import os
os.environ.setdefault("CIMG_CIM_PROFILE", "cim17v40")

from cimhub_raw.importer.raw34_to_cim import raw34_to_cim
from cimhub_raw.exporter.cim_to_raw import cim_to_raw

network = raw34_to_cim("network.raw", include_extensions=True)
cim_to_raw(network, "output.raw")
```

### 7. Serialize CIM to XML / JSON-LD

Once you have a CIM network model, serialize it to CIM XML or JSON-LD:

```python
from cimgraph.utils import write_xml, write_json_ld

write_xml(network, "network.xml")
write_json_ld(network, "network.jsonld")
```

---

## Equipment Coverage

### Fully converted (RAW ↔ CIM)

| RAW Record | CIM Class(es) |
|---|---|
| BusRecord | `TopologicalNode`, `ConnectivityNode` |
| LoadRecord | `EnergyConsumer` |
| GeneratorRecord | `SynchronousMachine` |
| FixedShuntRecord | `LinearShuntCompensator` (single-section) |
| SwitchedShuntRecord | `LinearShuntCompensator` (multi-section) |
| BranchRecord | `ACLineSegment`, `SeriesCompensator`, `Breaker` |
| TransformerRecord | `PowerTransformer` (2-winding) |
| TwoTerminalDCRecord | `DCLineSegment` + `CsConverter` x2 |
| VscDcRecord | `DCLineSegment` + `VsConverter` x2 |
| FactsDeviceRecord | `StaticVarCompensator`, `VsConverter` |
| InductionMachineRecord | `AsynchronousMachine` |
| AreaInterchangeRecord | `ControlArea` / `SubControlArea` |
| ZoneRecord | `SubGeographicalRegion` |
| OwnerRecord | `AssetOwner` |
| MultiSectionLineRecord | `Line` (container grouping) |
| SwitchRecord | `Switch` / `Breaker` |

### Passthrough only (read + write, no CIM conversion)

These record types are parsed and written verbatim but have no CIM mapping:

| RAW Record | Reason |
|---|---|
| InterareaTransferRecord | No standard CIM equivalent |
| ImpedanceCorrectionRecord | No CIM class in the CIM 17 profile |
| MultiTerminalDCRecord | Sub-records (converters, bus links) not fully parsed |
| GneDeviceRecord | Generic extensible model; depends on `model` field |

---

## Running Tests

```bash
# From the repo root
uv run pytest cimhub_raw/tests/

# Key test files
uv run pytest cimhub_raw/tests/test_raw34_roundtrip.py       # RAW → RAW (no CIM)
uv run pytest cimhub_raw/tests/test_ieee14_roundtrip.py      # Full RAW → CIM → RAW
uv run pytest cimhub_raw/tests/test_production_roundtrip.py  # 169-bus production model
```

---

## TODO

### CIM mappings needed for passthrough record types

These record types are currently read and written verbatim (no CIM conversion).
Implementing them requires either finding an appropriate CIM class or extending the data profile.

#### 1. `InterareaTransferRecord`
- Represents scheduled power transfers between control areas.
- No direct CIM equivalent in CIM 17. Closest candidates: `TieFlow` or a custom extension.
- Work needed: define CIM mapping, implement importer + exporter, add tests.

#### 2. `ImpedanceCorrectionRecord`
- Tap-dependent impedance correction tables for transformers.
- No CIM class in the CIM 17 profile. Closest: `TransformerMeshImpedance` or a raw_extension.
- Work needed: define CIM mapping or raw_extension class, implement importer + exporter, add tests.

#### 3. `MultiTerminalDCRecord`
- Represents multi-terminal HVDC lines (3+ converter stations).
- CIM has `DCNode`, `DCTopologicalNode`, `DCConverterUnit` — mapping is feasible but complex.
- Current reader only parses the header line; converter/bus/link sub-records are skipped.
- Work needed: extend reader to parse all sub-record types, define CIM mapping, implement importer + exporter.

#### 4. `GneDeviceRecord`
- Generic network element: a catch-all for vendor-specific dynamic models.
- No universal CIM mapping — depends on the `model` field value.
- Work needed: case-by-case assessment per model type; may require raw_extension approach.

### Other known gaps

- **3-winding transformers**: `TransformerRecord` currently handles 2-winding only. 3-winding transformers are represented as a star (3 × 2-winding) in raw; CIM uses `PowerTransformerEnd` with 3 ends. Needs dedicated handling.
- **Multi-terminal DC importer registration**: `MultiTerminalDCRecord` is not in `IMPORT_ORDER` in `raw34_to_cim.py`.



## Legacy PSS/E RAW extension layer (`cimgraph_raw_extension`). Provides raw-extension dataclasses that can be attached alongside CIM objects during a PSS/E import to preserve fields that have no direct CIM mapping.


### Usage

```python
from cimgraph_raw_extension.converters.rawToCim import raw_to_cim
from cimgraph_raw_extension.converters.cimToRaw import cim_to_raw

# Import RAW → CIM with extensions attached
network = raw_to_cim("network.raw")

# Export CIM → RAW
cim_to_raw(network, "output.raw")
```

## Structure

```
src/cimgraph_raw_extension/
    data_profile/raw_extension.py   # Extension dataclasses (RawBusRecord, etc.)
    models/rawModel.py              # RawModel container (legacy)
    converters/rawToCim.py          # RAW → CIM conversion
    converters/cimToRaw.py          # CIM → RAW conversion
```

