Multiply a 2×2 matrix by a length-2 vector by hand. An outer loop over rows feeds an inner loop that accumulates one dot product per row into s. The trace shows s resetting to 0 at each new row, then building up before being appended to result.

By hand

Outer loop over each row of A. Inner loop over each column index i: accumulate row[i] * v[i] into s. After the inner loop, append s to result. Each s is the dot product of one row with v.

naive.py
Replay: real traced execution (multi-file project)
A = [[1, 2], [3, 4]]
v = [5, 6]
result = []
for row in A:
    s = 0
    for i in range(len(v)):
        s += row[i] * v[i]
    result.append(s)
print('RESULT:', result)
  1. A ← [[1, 2], [3, 4]]

    1A = [[1, 2], [3, 4]]2v = [5, 6]
    values this step[[1, 2], [3, 4]]A
  2. v ← [5, 6]

    1A = [[1, 2], [3, 4]]2v = [5, 6]3result = []
    values this step[5, 6]v
  3. result ← []

    2v = [5, 6]3result = []4for row in A:
    values this step[]result
  4. row ← [1, 2]

    3result = []4for row in A:5    s = 0
    values this step[1, 2]row
  5. s ← 0

    4for row in A:5    s = 06    for i in range(len(v)):
    values this step0s
  6. i ← 0

    5s = 06for i in range(len(v)):7    s += row[i] * v[i]
    values this step0i
  7. s ← 5

    6for i in range(len(v)):7    s += row[i] * v[i]8result.append(s)
    values this step0 5s
  8. i ← 1

    5s = 06for i in range(len(v)):7    s += row[i] * v[i]
    values this step0 1i
  9. s ← 17

    6for i in range(len(v)):7    s += row[i] * v[i]8result.append(s)
    values this step5 17s
  10. for i in range(len(v)):

    5s = 06for i in range(len(v)):7    s += row[i] * v[i]
  11. result ← [17]

    7        s += row[i] * v[i]8    result.append(s)9print('RESULT:', result)
    values this step[] [17]result
  12. row ← [3, 4]

    3result = []4for row in A:5    s = 0
    values this step[1, 2] [3, 4]row
  13. s ← 0

    4for row in A:5    s = 06    for i in range(len(v)):
    values this step17 0s
  14. i ← 0

    5s = 06for i in range(len(v)):7    s += row[i] * v[i]
    values this step1 0i
  15. s ← 15

    6for i in range(len(v)):7    s += row[i] * v[i]8result.append(s)
    values this step0 15s
  16. i ← 1

    5s = 06for i in range(len(v)):7    s += row[i] * v[i]
    values this step0 1i
  17. s ← 39

    6for i in range(len(v)):7    s += row[i] * v[i]8result.append(s)
    values this step15 39s
  18. for i in range(len(v)):

    5s = 06for i in range(len(v)):7    s += row[i] * v[i]
  19. result ← [17, 39]

    7        s += row[i] * v[i]8    result.append(s)9print('RESULT:', result)
    values this step[17] [17, 39]result
  20. for row in A:

    3result = []4for row in A:5    s = 0
  21. stdout ← RESULT: [17, 39]

    8    result.append(s)9print('RESULT:', result)
    values this stepRESULT: [17, 39]stdout

With NumPy

mat @ vec applies the @ (matmul) operator: each element of the output is the dot product of the corresponding row of mat with vec. The snapshot shows the matrix, the vector, and the result with their shapes.

library.py
import numpy as np

A = [[1, 2], [3, 4]]
v = [5, 6]
mat = np.array(A)
vec = np.array(v)
result = mat @ vec
print('mat: shape:', mat.shape, 'dtype:', mat.dtype, 'values:', mat.tolist())
print('vec: shape:', vec.shape, 'dtype:', vec.dtype, 'values:', vec.tolist())
print('result: shape:', result.shape, 'dtype:', result.dtype, 'values:', result.tolist())
print('RESULT:', result.tolist())
mat: shape: (2, 2) dtype: int64 values: [[1, 2], [3, 4]]
vec: shape: (2,) dtype: int64 values: [5, 6]
result: shape: (2,) dtype: int64 values: [17, 39]
RESULT: [17, 39]

Implementation notes

  • Each element of the output is a dot product: result[0] = row0 · vec, result[1] = row1 · vec. The hand-written loop makes this explicit — see dot-product for the scalar version.
  • The shapes must be compatible: (m, n) @ (n,)(m,). Here (2, 2) @ (2,)(2,). A shape mismatch raises a ValueError.
  • @ dispatches to np.matmul for 2-D arrays. For a matrix times a matrix use the same @ operator: (m, n) @ (n, p)(m, p).
  • Shape, dtype, and values are shown explicitly here because ndarray.__repr__ output varies with NumPy version and print options.