Frames#
Frame structure objects (decision D28).
A frame is described by a FrameStructure: an ordered list of
typed fields (FrameField with a FieldRole), shared by
the Framer and the Deframer exactly like a
CarrierAllocation is shared by the carrier allocator and extractor.
FieldRole types the time axis the way CarrierType types the
frequency axis.
- class FrameField(name: str, role: FieldRole, values: ndarray | None = None, *, length: int | None = None)[source]#
One typed field of a frame.
- Parameters:
name (str) – Field name (“STF”, “LTF”, “SIG”, “PAYLOAD”…).
role (FieldRole) – What the field is for.
values (np.ndarray, optional) – Transmitted samples.
Nonemeans unknown at transmit time – only allowed for the PAYLOAD field, whose length is then given bylength.length (int, optional) – Field length in samples; inferred from
valueswhen provided.
- property size: int#
Field length in samples (guaranteed set after construction).
- class FrameStructure(fields: tuple[FrameField, ...], *, standard: str = 'custom', reference: str = '')[source]#
Ordered list of typed frame fields (decision D28).
Invariants: exactly one PAYLOAD field, and every other field carries its transmitted values.
Examples
>>> from comnumpy.core.sequences import zadoff_chu >>> frame = FrameStructure(( ... FrameField("SYNC", FieldRole.SYNC, zadoff_chu(1, 63)), ... FrameField("PAYLOAD", FieldRole.PAYLOAD, length=1000), ... ), standard="example") >>> frame.frame_length, frame.payload_length (1063, 1000)
- class Framer(frame: FrameStructure, *, name: str = 'framer')[source]#
Insert the frame fields around the payload (decision D28).
Signal Model#
\[\mathbf{y} = [\, \mathbf{s}_1 \,|\, \cdots \,|\, \mathbf{x} \,|\, \cdots \,|\, \mathbf{s}_M \,]\]where the \(\mathbf{s}_i\) are the known field values of the shared
FrameStructureand \(\mathbf{x}\) is the payload.The model is structural rather than analytical: no arithmetic is performed on the samples. Writing the frame as the ordered list of its \(M\) typed fields, of lengths \(L_1, \dots, L_M\), the output row of index \(t\) is the concatenation
\[\begin{split}y_t[n] = \begin{cases} s_i\left[n - \sum_{j<i} L_j\right] & \text{if field } i \text{ is known at TX} \\ x_t\left[n - \sum_{j<i} L_j\right] & \text{if field } i \text{ is the PAYLOAD} \end{cases}\end{split}\]for \(0 \le n < N_{\mathrm{frame}} = \sum_i L_i\). Exactly one field carries the role
PAYLOADand receives the input; every other field (SYNC,TRAINING,HEADER,TAIL,PAD) carries values fixed at construction time. The output is complex, since the known fields generally are.Axes: declared axis – Block layout
(..., T, N_payload)with the frame index onT; fields are added on the content axis -1. The S/P block size is not a free parameter: it equalsframe.payload_lengthandprepare()enforces it.- param frame:
Frame description shared with the receiver’s
Deframer. Itspayload_lengthfixes the accepted input size \(N_{\mathrm{payload}}\) and itsframe_lengththe output size \(N_{\mathrm{frame}}\).- type frame:
FrameStructure
- param name:
Name of the framer instance. Default is
"framer".- type name:
str, optional, keyword-only
- raises ShapeError:
If the last axis of the input does not equal
frame.payload_length.
References
IEEE Std 802.11-2020, Clause 17 (Orthogonal frequency division multiplexing PHY) – example of a real typed frame structure: short training field, long training field, SIGNAL field, then DATA.
Examples
>>> frame = FrameStructure(( ... FrameField("SYNC", FieldRole.SYNC, np.array([1.0, -1.0])), ... FrameField("PAYLOAD", FieldRole.PAYLOAD, length=3), ... ), standard="example") >>> framer = Framer(frame) >>> print(framer(np.array([[1.0, 2.0, 3.0]]))) [[ 1.+0.j -1.+0.j 1.+0.j 2.+0.j 3.+0.j]]
- class Deframer(frame: FrameStructure, *, name: str = 'deframer')[source]#
Extract the payload (and expose every field) from received frames.
Signal Model#
The exact inverse of
Framer, and equally structural: the received frame is cut back along the field boundaries of the sharedFrameStructure. Field \(i\) occupies the slice \([\sum_{j<i} L_j, \; \sum_{j \le i} L_j)\) of the frame axis,\[\hat{\mathbf{s}}_i = \mathbf{y}\!\left[\, \sum_{j<i} L_j : \, \sum_{j \le i} L_j \,\right], \qquad i = 1, \dots, M\]and
forwardreturns the single field whose role isPAYLOAD:\[\hat{\mathbf{x}} = \hat{\mathbf{s}}_{i_\mathrm{payload}}\]The other fields are not discarded: the received \(\hat{\mathbf{s}}_i\) are recorded and read back with
get_field()– that is how a receiver gets theSYNCfield for timing or theTRAININGfield for channel estimation without adding a block to the chain (D42).Axes: declared axis – Block layout
(..., T, frame_length). Afterforward, the received samples of each field are available throughget_field().- param frame:
Frame description, the very same object as the transmitter’s
Framer. Itsframe_lengthfixes the accepted input size \(N_{\mathrm{frame}}\).- type frame:
FrameStructure
- param name:
Name of the deframer instance. Default is
"deframer".- type name:
str, optional, keyword-only
- raises ShapeError:
If the last axis of the input does not equal
frame.frame_length.
References
IEEE Std 802.11-2020, Clause 17 (Orthogonal frequency division multiplexing PHY) – example of a real typed frame structure: short training field, long training field, SIGNAL field, then DATA.
Examples
>>> frame = FrameStructure(( ... FrameField("SYNC", FieldRole.SYNC, np.array([1.0, -1.0])), ... FrameField("PAYLOAD", FieldRole.PAYLOAD, length=3), ... ), standard="example") >>> deframer = Deframer(frame) >>> print(deframer(np.array([[1.0, -1.0, 1.0, 2.0, 3.0]]))) [[1. 2. 3.]] >>> print(deframer.get_field("SYNC")) [[ 1. -1.]]