Home Manual Reference Source Test

src/model/constraints2.js

  1. import { EDGES } from "./theory.js";
  2. /**
  3. * Builds the MiniZinc COP for Step 2:
  4. * shortest path of fixed length over the chord graph.
  5. * MZN nodes are 1-indexed: solver path int i refers to graph.nodes[i-1].
  6. * @param {Object} parameters - Step-2 form params; see {@link Step2Chords#schema}.
  7. * @param {Object} graph - Chord graph from {@link chordGraph}.
  8. * @return {string} MiniZinc source.
  9. */
  10. export function modelChords(parameters, graph) {
  11. const pathLength = parameters.bars;
  12. const creativity = parameters.creativity;
  13. const { nodes } = graph;
  14. const costMatrix = buildCostMatrix(graph, creativity);
  15. // Fallback to graph tonic when chord is opposite-mode or inactive in the graph.
  16. const oppositeTonic = graph.tonic === "I" ? "i" : "I";
  17. const resolve = (chord) =>
  18. graph.activeNodes.includes(chord) && chord !== oppositeTonic
  19. ? chord
  20. : graph.tonic;
  21. const startNode = nodes.indexOf(resolve(parameters.start_chord)) + 1;
  22. const endNode = nodes.indexOf(resolve(parameters.end_chord)) + 1;
  23. return [
  24. `int: n_nodes = ${nodes.length};`,
  25. `int: path_length = ${pathLength};`,
  26. `int: start_node = ${startNode};`,
  27. `int: end_node = ${endNode};`,
  28. "set of int: NODES = 1..n_nodes;",
  29. "set of int: PATH_INDICES = 1..path_length;",
  30. "array[NODES, NODES] of int: cost_matrix = [|",
  31. ...costMatrix.map(
  32. (row, r) => ` ${row.join(", ")}${r < costMatrix.length - 1 ? " |" : ""}`,
  33. ),
  34. "|];",
  35. "array[PATH_INDICES] of var NODES: path;",
  36. "var int: total_cost;",
  37. ...startEndChord(),
  38. ...minimizeCost(pathLength),
  39. ].join("\n");
  40. }
  41. /** @private Cost matrix with creativity-based (upsilon) randomization. */
  42. function buildCostMatrix(graph, creativity) {
  43. const { nodes, edges, costs } = graph;
  44. const matrix = Array.from({ length: nodes.length }, () =>
  45. Array(nodes.length).fill(EDGES.MISSING),
  46. );
  47. const realCosts = edges
  48. .filter(([u, v]) => u !== v)
  49. .map(([u, v]) => costs.get(`${u}->${v}`));
  50. const minCost = Math.min(...realCosts);
  51. const maxCost = Math.max(...realCosts);
  52. for (const [u, v] of edges) {
  53. let cost = costs.get(`${u}->${v}`);
  54. if (creativity > 0) {
  55. const rand = minCost + Math.random() * (maxCost - minCost);
  56. cost = Math.max(
  57. 1,
  58. Math.round((1 - creativity) * cost + creativity * rand),
  59. );
  60. }
  61. matrix[nodes.indexOf(u)][nodes.indexOf(v)] = cost;
  62. }
  63. return matrix;
  64. }
  65. /** @private F4: path starts at start_node (psi), ends at end_node (omega). */
  66. function startEndChord() {
  67. return [
  68. "constraint path[1] = start_node;",
  69. "constraint path[path_length] = end_node;",
  70. ];
  71. }
  72. /**
  73. * @private H20: minimize total path (gamma_0,...,gamma_iota) cost.
  74. * For pathLength <= 2 the path is fully determined by start/end;
  75. * minimize on a determined model crashes MiniZinc-Wasm, so we fall back to satisfy.
  76. */
  77. function minimizeCost(pathLength) {
  78. return [
  79. "constraint total_cost = sum(i in 1..(path_length - 1))(cost_matrix[path[i], path[i+1]]);",
  80. pathLength <= 2 ? "solve satisfy;" : "solve minimize total_cost;",
  81. ];
  82. }