from math import exp, sqrt

labels = ["A","B","C"]
q = [2.0, 1.0]
keys = [[-1.0, 2.0], [1.0, 0.0], [0.0, 1.0]]
values = [[2.0, 0.0], [0.0, 2.0], [-1.0, -1.0]]

# Float operands and left-to-right sums from 0.0 follow browser accumulation.
# Cancellation can hide a nonzero exact change. This is not exact arithmetic;
# exp/sqrt implementations and display rounding can differ across runtimes.
def ordered_sum(terms):
    total = 0.0
    for term in terms:
        total += term
    return total

d_k = len(q)  # key dimension, NOT number of sources or value width
raw_scores = [ordered_sum(qj * kj for qj, kj in zip(q, k)) for k in keys]
scores = [s / sqrt(d_k) for s in raw_scores]
exps = [exp(s - max(scores)) for s in scores]
total = ordered_sum(exps)
weights = [e / total for e in exps]
output = [ordered_sum(v[j] * w for w, v in zip(weights, values))
          for j in range(len(values[0]))]

for label, raw, score, weight in zip(labels, raw_scores, scores, weights):
    print(label, round(raw, 3), round(score, 3), round(weight, 3))
print("output", [round(x, 3) for x in output])
