const metrics = { alpha: { // combine sigma and bigTheta option: "Geometric Difference (α)", displayName: String.raw`\alpha`, displayBody: p => String.raw` \sigma\left(${p}\right) \Theta\left(${p}\right)^{ \left(C\left(\left\{\vec{q}\right\}\right) + L\left(\left\{\vec{q}\right\}\right)\right) } `, evaluate: () => 0, // calculated below }, sigma: { // RMS option: "RMS Deviation (σ)", displayName: String.raw`\sigma`, 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: { // 1 - arith mean of cosine similarity option: "Cosine Difference (Θ)", displayName: String.raw`\Theta`, displayBody: p => String.raw` 1 - \hat{q}\cdot\vec{\nu}\left(${p}\right) `, evaluate: (data, target) => 1 - vectorDot(data.nu, target.unit), }, theta: { // angle of mean option: "Angular Difference (θ)", displayName: String.raw`\theta`, 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 Azimuth (ϕ)", displayName: String.raw`\phi`, 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) => { const raw = Math.abs(data.mu.hue - target.hue); return Math.min(raw, 360 - raw); }, }, delta: { // euclidean option: "Euclidean (δ)", displayName: String.raw`\delta`, displayBody: p => String.raw` \left|\left| \vec{q} - \vec{\mu}\left(${p}\right) \right|\right| `, evaluate: (data, target) => vectorMag(data.mu.vector.map((x, i) => x - target.vector[i])), }, manhattan: { // manhattan distance option: "Manhattan (M)", displayName: "M", displayBody: p => String.raw` \sum_{i} \left| \vec{\mu}\left(${p}\right)_i - \vec{q}_i \right| `, evaluate: (data, target) => data.mu.vector.map((x, i) => Math.abs(x - target.vector[i])).reduce((x, y) => x + y), }, ch: { // chebyshev option: "Chebyshev (Ч)", displayName: "Ч", 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 (ℓ)", displayName: String.raw`\ell`, displayBody: (p, space) => String.raw` \left| ${space === "jab" ? "\\frac{1}{100}" : ""}\text{comp}_{\vec{${space === "jab" ? "J" : "L"}}}{\vec{q}} - ${space === "jab" ? "\\frac{1}{100}" : ""}\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: "I", displayBody: p => String.raw` \frac{1}{\left|${p}\right|} \sum_{p\in ${p}}{\left|\left|\vec{p}\right|\right|^2} `, evaluate: data => data.inertia, }, muNuAngle: { option: "Mu-Nu Angle (V)", displayName: "V", displayBody: p => String.raw`\angle \left( \vec{\mu}\left(${p}\right), \vec{\nu}\left(${p}\right) \right)`, evaluate: data => data.muNuAngle, }, size: { option: "Size (N)", displayName: "N", displayBody: p => String.raw`\left|${p}\right|`, evaluate: data => data.size, }, lightness: { option: "Mean Lightness (L)", displayName: "L", displayBody: (p, space) => String.raw` ${space === "jab" ? "\\frac{1}{100}" : ""}\text{comp}_{\vec{${space === "jab" ? "J" : "L"}}}{\vec{\mu}\left(${p}\right)} `, evaluate: data => data.mu.lightness, }, chroma: { option: "Mean Chroma (C)", displayName: "C", displayBody: p => String.raw`\text{chroma}\left(\vec{\mu}\left(${p}\right)\right)`, evaluate: data => data.mu.chroma, }, importance: { option: "Visual Importance (β)", displayName: String.raw`\beta`, displayBody: p => String.raw` \begin{aligned} &C\left(${p}\right) + L\left(${p}\right) + \frac{\left|${p}\right|}{\left|P\right|}\\ + &\tanh{\left(100\left(C\left(${p}\right) - 0.25\right)\right)} \\ + &\tanh{\left(100\left(C\left(${p}\right) - 0.4\right)\right)} \\ + &\tanh{\left(100\left(L\left(${p}\right) - 0.5\right)\right)} \\ + &\tanh{\left(100\left(\frac{\left|${p}\right|}{\left|P\right|} - 0.05\right)\right)} \\ + &\tanh{\left(100\left(\frac{\left|${p}\right|}{\left|P\right|} - 0.10\right)\right)} \\ + &\tanh{\left(100\left(\frac{\left|${p}\right|}{\left|P\right|} - 0.15\right)\right)} \\ + &\tanh{\left(100\left(\frac{\left|${p}\right|}{\left|P\right|} - 0.25\right)\right)} \\ + &\tanh{\left(100\left(\frac{\left|${p}\right|}{\left|P\right|} - 0.80\right)\right)} \end{aligned} `, evaluate: data => data.importance, }, }; const applyMetrics = (data, target) => { const scores = Object.fromEntries( Object.entries(metrics) .map(([name, metric]) => [name, metric.evaluate(data, target)]) ); scores.alpha = scores.sigma * Math.pow(scores.bigTheta, target.chroma + target.lightness); return scores; };