const metrics = { sigma: { // RMS option: "RMS Deviation (σ)", displayName: p => String.raw`\sigma\left(${p}\right)`, displayBody: p => String.raw` \sqrt{I\left(${p}\right) - 2\vec{q}\cdot\vec{\mu}\left(${p}\right) + \left|\left|\vec{q}\right|\right|^2} `, evaluate: (data, target) => Math.sqrt(data.inertia - 2 * vectorDot(data.mu.vector, target.vector) + target.sqMag), }, bigTheta: { // mean of angle option: "Mean of Angular Difference (Θ)", displayName: p => String.raw`\Theta\left(${p}\right)`, displayBody: p => String.raw` \cos^{-1}\left( \hat{q}\cdot\vec{\nu}\left(${p}\right) \right) `, evaluate: (data, target) => rad2deg * Math.acos(vectorDot(data.nu, target.unit)), }, theta: { // angle of mean option: "Angular Difference of Mean (θ)", displayName: p => String.raw`\theta\left(${p}\right)`, displayBody: p => String.raw` \cos^{-1}\left( \hat{q}\cdot\hat{\mu}\left(${p}\right) \right) `, evaluate: (data, target) => rad2deg * Math.acos(vectorDot(data.mu.unit, target.unit)), }, phi: { // hue angle option: "Hue Difference of Mean (ϕ)", displayName: p => String.raw`\phi\left(${p}\right)`, displayBody: (p, space) => String.raw` \angle \left(\text{oproj}_{\vec{${space === "jab" ? "J" : "L"}}}{\vec{q}}, \text{oproj}_{\vec{${space === "jab" ? "J" : "L"}}}{\vec{\mu}\left(${p}\right)} \right) `, evaluate: (data, target) => angleDiff(data.mu.hue, target.hue), }, delta: { // euclidean option: "Euclidean Distance to Mean (δ)", displayName: p => String.raw`\delta\left(${p}\right)`, displayBody: p => String.raw` \left|\left| \vec{q} - \vec{\mu}\left(${p}\right) \right|\right| `, evaluate: (data, target) => vectorDist(data.mu.vector, target.vector), }, ch: { // chebyshev option: "Chebyshev Distance to Mean (Ч)", displayName: p => String.raw`Ч\left(${p}\right)`, displayBody: p => String.raw` \max_{i} \left| \vec{\mu}\left(${p}\right)_i - \vec{q}_i \right| `, evaluate: (data, target) => Math.max(...data.mu.vector.map((x, i) => Math.abs(x - target.vector[i]))), }, lightnessDiff: { option: "Lightness Difference from Mean (ℓ)", displayName: p => String.raw`\ell\left(${p}\right)`, displayBody: (p, space) => String.raw` \left| \text{comp}_{\vec{${space === "jab" ? "J" : "L"}}}{\vec{q}} - \text{comp}_{\vec{${space === "jab" ? "J" : "L"}}}{\vec{\mu}\left(${p}\right)} \right| `, evaluate: (data, target) => Math.abs(data.mu.lightness - target.lightness), }, inertia: { option: "Inertia (I)", displayName: p => String.raw`I\left(${p}\right)`, displayBody: p => String.raw` \frac{1}{\left|${p}\right|} \sum_{p\in ${p}}{\left|\left|\vec{p}\right|\right|^2} `, evaluate: data => data.inertia, }, lightness: { option: "Mean Lightness (ℒ)", displayName: p => String.raw`\mathcal{L}\left(${p}\right)`, displayBody: (p, space) => String.raw` \text{comp}_{\vec{${space === "jab" ? "J" : "L"}}}{\vec{\mu}\left(${p}\right)} `, evaluate: data => data.mu.lightness, }, muNuAngle: { option: "Mu-Nu Angle (V)", displayName: p => String.raw`V\left(${p}\right)`, displayBody: p => String.raw`\angle \left( \vec{\mu}\left(${p}\right), \vec{\nu}\left(${p}\right) \right)`, evaluate: data => rad2deg * Math.acos(vectorDot(data.mu.unit, data.nu) / vectorMag(data.nu)), }, size: { option: "Size (N)", displayName: p => String.raw`N\left(${p}\right)`, displayBody: p => String.raw`\left|${p}\right|`, evaluate: data => data.size, } }; const applyMetrics = (data, target) => Object.fromEntries( Object.entries(metrics).map(([name, metric]) => [name, metric.evaluate(data, target)]) );