qsharp-lang 1.27.3-dev → 1.27.5-dev

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (342) hide show
  1. package/README.md +3 -5
  2. package/dist/browser.d.ts +1 -0
  3. package/dist/browser.js +5 -0
  4. package/dist/compiler/worker.js +0 -1
  5. package/dist/debug-service/worker.js +0 -1
  6. package/dist/language-service/worker.js +0 -1
  7. package/dist/main.d.ts +4 -4
  8. package/dist/main.js +4 -8
  9. package/dist/node.js +2 -7
  10. package/dist/workers/adapters/browser.d.ts +9 -0
  11. package/dist/workers/adapters/browser.js +29 -0
  12. package/dist/workers/adapters/node.d.ts +9 -0
  13. package/dist/workers/adapters/node.js +34 -0
  14. package/dist/workers/adapters/types.d.ts +14 -0
  15. package/dist/workers/adapters/types.js +3 -0
  16. package/dist/workers/main.d.ts +3 -3
  17. package/dist/workers/main.js +5 -10
  18. package/dist/workers/worker.d.ts +4 -3
  19. package/dist/workers/worker.js +8 -6
  20. package/docs/Microsoft.Quantum.Core/IsRangeEmpty.md +1 -1
  21. package/docs/Microsoft.Quantum.Core/Length.md +1 -1
  22. package/docs/Microsoft.Quantum.Core/RangeEnd.md +1 -1
  23. package/docs/Microsoft.Quantum.Core/RangeStart.md +1 -1
  24. package/docs/Microsoft.Quantum.Core/Repeated.md +1 -1
  25. package/docs/Microsoft.Quantum.Core/index.md +1 -1
  26. package/docs/Std.Arithmetic/AddLE.md +1 -1
  27. package/docs/Std.Arithmetic/ApplyIfEqualL.md +1 -1
  28. package/docs/Std.Arithmetic/ApplyIfEqualLE.md +1 -1
  29. package/docs/Std.Arithmetic/ApplyIfGreaterL.md +1 -1
  30. package/docs/Std.Arithmetic/ApplyIfGreaterLE.md +1 -1
  31. package/docs/Std.Arithmetic/ApplyIfGreaterOrEqualL.md +1 -1
  32. package/docs/Std.Arithmetic/ApplyIfGreaterOrEqualLE.md +1 -1
  33. package/docs/Std.Arithmetic/ApplyIfLessL.md +1 -1
  34. package/docs/Std.Arithmetic/ApplyIfLessLE.md +1 -1
  35. package/docs/Std.Arithmetic/ApplyIfLessOrEqualL.md +1 -1
  36. package/docs/Std.Arithmetic/ApplyIfLessOrEqualLE.md +1 -1
  37. package/docs/Std.Arithmetic/FourierTDIncByLE.md +1 -1
  38. package/docs/Std.Arithmetic/IncByI.md +1 -1
  39. package/docs/Std.Arithmetic/IncByIUsingIncByLE.md +1 -1
  40. package/docs/Std.Arithmetic/IncByL.md +1 -1
  41. package/docs/Std.Arithmetic/IncByLE.md +1 -1
  42. package/docs/Std.Arithmetic/IncByLEUsingAddLE.md +1 -1
  43. package/docs/Std.Arithmetic/IncByLUsingIncByLE.md +1 -1
  44. package/docs/Std.Arithmetic/LookAheadDKRSAddLE.md +1 -1
  45. package/docs/Std.Arithmetic/MAJ.md +1 -1
  46. package/docs/Std.Arithmetic/ReflectAboutInteger.md +1 -1
  47. package/docs/Std.Arithmetic/RippleCarryCGAddLE.md +1 -1
  48. package/docs/Std.Arithmetic/RippleCarryCGIncByLE.md +1 -1
  49. package/docs/Std.Arithmetic/RippleCarryTTKIncByLE.md +1 -1
  50. package/docs/Std.Arithmetic/index.md +1 -1
  51. package/docs/Std.Arrays/All.md +1 -1
  52. package/docs/Std.Arrays/Any.md +1 -1
  53. package/docs/Std.Arrays/Chunks.md +1 -1
  54. package/docs/Std.Arrays/CircularlyShifted.md +1 -1
  55. package/docs/Std.Arrays/ColumnAt.md +1 -1
  56. package/docs/Std.Arrays/Count.md +1 -1
  57. package/docs/Std.Arrays/Diagonal.md +1 -1
  58. package/docs/Std.Arrays/DrawMany.md +1 -1
  59. package/docs/Std.Arrays/Enumerated.md +1 -1
  60. package/docs/Std.Arrays/Excluding.md +1 -1
  61. package/docs/Std.Arrays/Filtered.md +1 -1
  62. package/docs/Std.Arrays/FlatMapped.md +1 -1
  63. package/docs/Std.Arrays/Flattened.md +1 -1
  64. package/docs/Std.Arrays/Fold.md +1 -1
  65. package/docs/Std.Arrays/ForEach.md +1 -1
  66. package/docs/Std.Arrays/Head.md +1 -1
  67. package/docs/Std.Arrays/HeadAndRest.md +1 -1
  68. package/docs/Std.Arrays/IndexOf.md +1 -1
  69. package/docs/Std.Arrays/IndexRange.md +1 -1
  70. package/docs/Std.Arrays/Interleaved.md +1 -1
  71. package/docs/Std.Arrays/IsEmpty.md +1 -1
  72. package/docs/Std.Arrays/IsRectangularArray.md +1 -1
  73. package/docs/Std.Arrays/IsSorted.md +1 -1
  74. package/docs/Std.Arrays/IsSquareArray.md +1 -1
  75. package/docs/Std.Arrays/Mapped.md +1 -1
  76. package/docs/Std.Arrays/MappedByIndex.md +1 -1
  77. package/docs/Std.Arrays/MappedOverRange.md +1 -1
  78. package/docs/Std.Arrays/Most.md +1 -1
  79. package/docs/Std.Arrays/MostAndTail.md +1 -1
  80. package/docs/Std.Arrays/Padded.md +1 -1
  81. package/docs/Std.Arrays/Partitioned.md +1 -1
  82. package/docs/Std.Arrays/Rest.md +1 -1
  83. package/docs/Std.Arrays/Reversed.md +1 -1
  84. package/docs/Std.Arrays/SequenceI.md +1 -1
  85. package/docs/Std.Arrays/SequenceL.md +1 -1
  86. package/docs/Std.Arrays/Sorted.md +1 -1
  87. package/docs/Std.Arrays/Subarray.md +1 -1
  88. package/docs/Std.Arrays/Swapped.md +1 -1
  89. package/docs/Std.Arrays/Tail.md +1 -1
  90. package/docs/Std.Arrays/Transposed.md +1 -1
  91. package/docs/Std.Arrays/Unzipped.md +1 -1
  92. package/docs/Std.Arrays/Where.md +1 -1
  93. package/docs/Std.Arrays/Windows.md +1 -1
  94. package/docs/Std.Arrays/Zipped.md +1 -1
  95. package/docs/Std.Arrays/index.md +1 -1
  96. package/docs/Std.Canon/ApplyCNOTChain.md +1 -1
  97. package/docs/Std.Canon/ApplyControlledOnBitString.md +1 -1
  98. package/docs/Std.Canon/ApplyControlledOnInt.md +1 -1
  99. package/docs/Std.Canon/ApplyOperationPowerA.md +1 -1
  100. package/docs/Std.Canon/ApplyOperationPowerCA.md +1 -1
  101. package/docs/Std.Canon/ApplyP.md +1 -1
  102. package/docs/Std.Canon/ApplyPauli.md +1 -1
  103. package/docs/Std.Canon/ApplyPauliFromBitString.md +1 -1
  104. package/docs/Std.Canon/ApplyPauliFromInt.md +1 -1
  105. package/docs/Std.Canon/ApplyQFT.md +1 -1
  106. package/docs/Std.Canon/ApplyQPE.md +1 -1
  107. package/docs/Std.Canon/ApplyToEach.md +1 -1
  108. package/docs/Std.Canon/ApplyToEachA.md +1 -1
  109. package/docs/Std.Canon/ApplyToEachC.md +1 -1
  110. package/docs/Std.Canon/ApplyToEachCA.md +1 -1
  111. package/docs/Std.Canon/ApplyXorInPlace.md +1 -1
  112. package/docs/Std.Canon/ApplyXorInPlaceL.md +1 -1
  113. package/docs/Std.Canon/CX.md +1 -1
  114. package/docs/Std.Canon/CY.md +1 -1
  115. package/docs/Std.Canon/CZ.md +1 -1
  116. package/docs/Std.Canon/Fst.md +1 -1
  117. package/docs/Std.Canon/MapPauliAxis.md +1 -1
  118. package/docs/Std.Canon/Relabel.md +1 -1
  119. package/docs/Std.Canon/Snd.md +1 -1
  120. package/docs/Std.Canon/SwapReverseRegister.md +1 -1
  121. package/docs/Std.Canon/index.md +1 -1
  122. package/docs/Std.Convert/BigIntAsBoolArray.md +1 -1
  123. package/docs/Std.Convert/BigIntAsInt.md +1 -1
  124. package/docs/Std.Convert/BoolArrayAsBigInt.md +1 -1
  125. package/docs/Std.Convert/BoolArrayAsInt.md +1 -1
  126. package/docs/Std.Convert/BoolArrayAsResultArray.md +1 -1
  127. package/docs/Std.Convert/BoolAsResult.md +1 -1
  128. package/docs/Std.Convert/ComplexAsComplexPolar.md +1 -1
  129. package/docs/Std.Convert/ComplexPolarAsComplex.md +1 -1
  130. package/docs/Std.Convert/DoubleAsStringWithPrecision.md +1 -1
  131. package/docs/Std.Convert/IntAsBigInt.md +1 -1
  132. package/docs/Std.Convert/IntAsBoolArray.md +1 -1
  133. package/docs/Std.Convert/IntAsDouble.md +1 -1
  134. package/docs/Std.Convert/ResultArrayAsBoolArray.md +1 -1
  135. package/docs/Std.Convert/ResultArrayAsInt.md +1 -1
  136. package/docs/Std.Convert/ResultAsBool.md +1 -1
  137. package/docs/Std.Convert/index.md +1 -1
  138. package/docs/Std.Core/Complex.md +1 -1
  139. package/docs/Std.Core/Length.md +1 -1
  140. package/docs/Std.Core/Repeated.md +1 -1
  141. package/docs/Std.Core/index.md +1 -1
  142. package/docs/Std.Diagnostics/ApplyIdleNoise.md +1 -1
  143. package/docs/Std.Diagnostics/BitFlipNoise.md +1 -1
  144. package/docs/Std.Diagnostics/CheckAllZero.md +1 -1
  145. package/docs/Std.Diagnostics/CheckOperationsAreEqual.md +1 -1
  146. package/docs/Std.Diagnostics/CheckZero.md +1 -1
  147. package/docs/Std.Diagnostics/ConfigurePauliNoise.md +1 -1
  148. package/docs/Std.Diagnostics/ConfigureQubitLoss.md +1 -1
  149. package/docs/Std.Diagnostics/DepolarizingNoise.md +1 -1
  150. package/docs/Std.Diagnostics/DumpMachine.md +1 -1
  151. package/docs/Std.Diagnostics/DumpOperation.md +1 -1
  152. package/docs/Std.Diagnostics/DumpRegister.md +1 -1
  153. package/docs/Std.Diagnostics/Fact.md +1 -1
  154. package/docs/Std.Diagnostics/NoNoise.md +1 -1
  155. package/docs/Std.Diagnostics/PhaseFlipNoise.md +1 -1
  156. package/docs/Std.Diagnostics/PostSelectZ.md +1 -1
  157. package/docs/Std.Diagnostics/StartCountingFunction.md +1 -1
  158. package/docs/Std.Diagnostics/StartCountingOperation.md +1 -1
  159. package/docs/Std.Diagnostics/StartCountingQubits.md +1 -1
  160. package/docs/Std.Diagnostics/StopCountingFunction.md +1 -1
  161. package/docs/Std.Diagnostics/StopCountingOperation.md +1 -1
  162. package/docs/Std.Diagnostics/StopCountingQubits.md +1 -1
  163. package/docs/Std.Diagnostics/index.md +1 -1
  164. package/docs/Std.Intrinsic/AND.md +1 -1
  165. package/docs/Std.Intrinsic/ApplyUnitary.md +1 -1
  166. package/docs/Std.Intrinsic/CCNOT.md +1 -1
  167. package/docs/Std.Intrinsic/CNOT.md +1 -1
  168. package/docs/Std.Intrinsic/Exp.md +1 -1
  169. package/docs/Std.Intrinsic/H.md +1 -1
  170. package/docs/Std.Intrinsic/I.md +1 -1
  171. package/docs/Std.Intrinsic/M.md +1 -1
  172. package/docs/Std.Intrinsic/Measure.md +1 -1
  173. package/docs/Std.Intrinsic/Message.md +1 -1
  174. package/docs/Std.Intrinsic/R.md +1 -1
  175. package/docs/Std.Intrinsic/R1.md +1 -1
  176. package/docs/Std.Intrinsic/R1Frac.md +1 -1
  177. package/docs/Std.Intrinsic/RFrac.md +1 -1
  178. package/docs/Std.Intrinsic/Reset.md +1 -1
  179. package/docs/Std.Intrinsic/ResetAll.md +1 -1
  180. package/docs/Std.Intrinsic/Rx.md +1 -1
  181. package/docs/Std.Intrinsic/Rxx.md +1 -1
  182. package/docs/Std.Intrinsic/Ry.md +1 -1
  183. package/docs/Std.Intrinsic/Ryy.md +1 -1
  184. package/docs/Std.Intrinsic/Rz.md +1 -1
  185. package/docs/Std.Intrinsic/Rzz.md +1 -1
  186. package/docs/Std.Intrinsic/S.md +1 -1
  187. package/docs/Std.Intrinsic/SWAP.md +1 -1
  188. package/docs/Std.Intrinsic/SX.md +1 -1
  189. package/docs/Std.Intrinsic/T.md +1 -1
  190. package/docs/Std.Intrinsic/X.md +1 -1
  191. package/docs/Std.Intrinsic/Y.md +1 -1
  192. package/docs/Std.Intrinsic/Z.md +1 -1
  193. package/docs/Std.Intrinsic/index.md +1 -1
  194. package/docs/Std.Logical/Xor.md +1 -1
  195. package/docs/Std.Logical/index.md +1 -1
  196. package/docs/Std.Math/AbsComplex.md +1 -1
  197. package/docs/Std.Math/AbsComplexPolar.md +1 -1
  198. package/docs/Std.Math/AbsD.md +1 -1
  199. package/docs/Std.Math/AbsI.md +1 -1
  200. package/docs/Std.Math/AbsL.md +1 -1
  201. package/docs/Std.Math/AbsSquaredComplex.md +1 -1
  202. package/docs/Std.Math/AbsSquaredComplexPolar.md +1 -1
  203. package/docs/Std.Math/ApproximateFactorial.md +1 -1
  204. package/docs/Std.Math/ArcCos.md +1 -1
  205. package/docs/Std.Math/ArcCosh.md +1 -1
  206. package/docs/Std.Math/ArcSin.md +1 -1
  207. package/docs/Std.Math/ArcSinh.md +1 -1
  208. package/docs/Std.Math/ArcTan.md +1 -1
  209. package/docs/Std.Math/ArcTan2.md +1 -1
  210. package/docs/Std.Math/ArcTanh.md +1 -1
  211. package/docs/Std.Math/ArgComplex.md +1 -1
  212. package/docs/Std.Math/ArgComplexPolar.md +1 -1
  213. package/docs/Std.Math/Binom.md +1 -1
  214. package/docs/Std.Math/BitSizeI.md +1 -1
  215. package/docs/Std.Math/BitSizeL.md +1 -1
  216. package/docs/Std.Math/Ceiling.md +1 -1
  217. package/docs/Std.Math/Complex.md +1 -1
  218. package/docs/Std.Math/ComplexPolar.md +1 -1
  219. package/docs/Std.Math/ContinuedFractionConvergentI.md +1 -1
  220. package/docs/Std.Math/ContinuedFractionConvergentL.md +1 -1
  221. package/docs/Std.Math/Cos.md +1 -1
  222. package/docs/Std.Math/Cosh.md +1 -1
  223. package/docs/Std.Math/DivRemI.md +1 -1
  224. package/docs/Std.Math/DivRemL.md +1 -1
  225. package/docs/Std.Math/DividedByC.md +1 -1
  226. package/docs/Std.Math/DividedByCP.md +1 -1
  227. package/docs/Std.Math/E.md +1 -1
  228. package/docs/Std.Math/ExpModI.md +1 -1
  229. package/docs/Std.Math/ExpModL.md +1 -1
  230. package/docs/Std.Math/ExtendedGreatestCommonDivisorI.md +1 -1
  231. package/docs/Std.Math/ExtendedGreatestCommonDivisorL.md +1 -1
  232. package/docs/Std.Math/FactorialI.md +1 -1
  233. package/docs/Std.Math/FactorialL.md +1 -1
  234. package/docs/Std.Math/Floor.md +1 -1
  235. package/docs/Std.Math/GreatestCommonDivisorI.md +1 -1
  236. package/docs/Std.Math/GreatestCommonDivisorL.md +1 -1
  237. package/docs/Std.Math/HammingWeightI.md +1 -1
  238. package/docs/Std.Math/InverseModI.md +1 -1
  239. package/docs/Std.Math/InverseModL.md +1 -1
  240. package/docs/Std.Math/IsCoprimeI.md +1 -1
  241. package/docs/Std.Math/IsCoprimeL.md +1 -1
  242. package/docs/Std.Math/IsInfinite.md +1 -1
  243. package/docs/Std.Math/IsNaN.md +1 -1
  244. package/docs/Std.Math/LargestFixedPoint.md +1 -1
  245. package/docs/Std.Math/Lg.md +1 -1
  246. package/docs/Std.Math/Log.md +1 -1
  247. package/docs/Std.Math/Log10.md +1 -1
  248. package/docs/Std.Math/LogFactorialD.md +1 -1
  249. package/docs/Std.Math/LogGammaD.md +1 -1
  250. package/docs/Std.Math/LogOf2.md +1 -1
  251. package/docs/Std.Math/Max.md +1 -1
  252. package/docs/Std.Math/MaxD.md +1 -1
  253. package/docs/Std.Math/MaxI.md +1 -1
  254. package/docs/Std.Math/MaxL.md +1 -1
  255. package/docs/Std.Math/Min.md +1 -1
  256. package/docs/Std.Math/MinD.md +1 -1
  257. package/docs/Std.Math/MinI.md +1 -1
  258. package/docs/Std.Math/MinL.md +1 -1
  259. package/docs/Std.Math/MinusC.md +1 -1
  260. package/docs/Std.Math/MinusCP.md +1 -1
  261. package/docs/Std.Math/ModulusI.md +1 -1
  262. package/docs/Std.Math/ModulusL.md +1 -1
  263. package/docs/Std.Math/NegationC.md +1 -1
  264. package/docs/Std.Math/NegationCP.md +1 -1
  265. package/docs/Std.Math/PI.md +1 -1
  266. package/docs/Std.Math/PNorm.md +1 -1
  267. package/docs/Std.Math/PNormalized.md +1 -1
  268. package/docs/Std.Math/PlusC.md +1 -1
  269. package/docs/Std.Math/PlusCP.md +1 -1
  270. package/docs/Std.Math/PowC.md +1 -1
  271. package/docs/Std.Math/PowCP.md +1 -1
  272. package/docs/Std.Math/RealMod.md +1 -1
  273. package/docs/Std.Math/Round.md +1 -1
  274. package/docs/Std.Math/RoundHalfAwayFromZero.md +1 -1
  275. package/docs/Std.Math/SignD.md +1 -1
  276. package/docs/Std.Math/SignI.md +1 -1
  277. package/docs/Std.Math/SignL.md +1 -1
  278. package/docs/Std.Math/Sin.md +1 -1
  279. package/docs/Std.Math/Sinh.md +1 -1
  280. package/docs/Std.Math/SmallestFixedPoint.md +1 -1
  281. package/docs/Std.Math/Sqrt.md +1 -1
  282. package/docs/Std.Math/SquaredNorm.md +1 -1
  283. package/docs/Std.Math/Tan.md +1 -1
  284. package/docs/Std.Math/Tanh.md +1 -1
  285. package/docs/Std.Math/TimesC.md +1 -1
  286. package/docs/Std.Math/TimesCP.md +1 -1
  287. package/docs/Std.Math/TrailingZeroCountI.md +1 -1
  288. package/docs/Std.Math/TrailingZeroCountL.md +1 -1
  289. package/docs/Std.Math/Truncate.md +1 -1
  290. package/docs/Std.Math/index.md +1 -1
  291. package/docs/Std.Measurement/IsLossResult.md +1 -1
  292. package/docs/Std.Measurement/MResetEachZ.md +1 -1
  293. package/docs/Std.Measurement/MResetX.md +1 -1
  294. package/docs/Std.Measurement/MResetY.md +1 -1
  295. package/docs/Std.Measurement/MResetZ.md +1 -1
  296. package/docs/Std.Measurement/MResetZChecked.md +1 -1
  297. package/docs/Std.Measurement/MeasureAllZ.md +1 -1
  298. package/docs/Std.Measurement/MeasureEachZ.md +1 -1
  299. package/docs/Std.Measurement/MeasureInteger.md +1 -1
  300. package/docs/Std.Measurement/index.md +1 -1
  301. package/docs/Std.Random/DrawRandomBool.md +1 -1
  302. package/docs/Std.Random/DrawRandomDouble.md +1 -1
  303. package/docs/Std.Random/DrawRandomInt.md +1 -1
  304. package/docs/Std.Random/index.md +1 -1
  305. package/docs/Std.Range/IsRangeEmpty.md +1 -1
  306. package/docs/Std.Range/RangeEnd.md +1 -1
  307. package/docs/Std.Range/RangeReverse.md +1 -1
  308. package/docs/Std.Range/RangeStart.md +1 -1
  309. package/docs/Std.Range/RangeStep.md +1 -1
  310. package/docs/Std.Range/index.md +1 -1
  311. package/docs/Std.ResourceEstimation/AccountForEstimates.md +1 -1
  312. package/docs/Std.ResourceEstimation/AuxQubitCount.md +1 -1
  313. package/docs/Std.ResourceEstimation/BeginEstimateCaching.md +1 -1
  314. package/docs/Std.ResourceEstimation/BeginRepeatEstimates.md +1 -1
  315. package/docs/Std.ResourceEstimation/CczCount.md +1 -1
  316. package/docs/Std.ResourceEstimation/EnableMemoryComputeArchitecture.md +1 -1
  317. package/docs/Std.ResourceEstimation/EndEstimateCaching.md +1 -1
  318. package/docs/Std.ResourceEstimation/EndRepeatEstimates.md +1 -1
  319. package/docs/Std.ResourceEstimation/LeastFrequentlyUsed.md +1 -1
  320. package/docs/Std.ResourceEstimation/LeastRecentlyUsed.md +1 -1
  321. package/docs/Std.ResourceEstimation/MeasurementCount.md +1 -1
  322. package/docs/Std.ResourceEstimation/PSSPCLayout.md +1 -1
  323. package/docs/Std.ResourceEstimation/RepeatEstimates.md +1 -1
  324. package/docs/Std.ResourceEstimation/RotationCount.md +1 -1
  325. package/docs/Std.ResourceEstimation/RotationDepth.md +1 -1
  326. package/docs/Std.ResourceEstimation/SingleVariant.md +1 -1
  327. package/docs/Std.ResourceEstimation/TCount.md +1 -1
  328. package/docs/Std.ResourceEstimation/index.md +1 -1
  329. package/docs/Std.StatePreparation/ApproximatelyPreparePureStateCP.md +1 -1
  330. package/docs/Std.StatePreparation/PreparePureStateD.md +1 -1
  331. package/docs/Std.StatePreparation/PrepareUniformSuperposition.md +1 -1
  332. package/docs/Std.StatePreparation/index.md +1 -1
  333. package/docs/Std.TableLookup/Select.md +1 -1
  334. package/docs/Std.TableLookup/index.md +1 -1
  335. package/docs/index.md +1 -1
  336. package/lib/web/qsc_wasm.d.ts +3 -3
  337. package/lib/web/qsc_wasm.js +7 -7
  338. package/lib/web/qsc_wasm_bg.wasm +0 -0
  339. package/package.json +3 -6
  340. package/ux/entanglement.tsx +1044 -0
  341. package/ux/index.ts +1 -0
  342. package/ux/qdk-theme.css +16 -2
@@ -0,0 +1,1044 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT License.
3
+
4
+ /**
5
+ * Generic chord diagram.
6
+ *
7
+ * Renders per-node scalar values and pairwise edge weights as an SVG chord
8
+ * diagram. Arc length is proportional to the node value; chord thickness
9
+ * is proportional to pairwise weight.
10
+ *
11
+ * `Entanglement` is a thin wrapper that supplies orbital-specific
12
+ * defaults (title, legend labels, colormaps, scale maxima).
13
+ */
14
+
15
+ import { useState, useRef, useEffect } from "preact/hooks";
16
+
17
+ // ---------------------------------------------------------------------------
18
+ // Types
19
+ // ---------------------------------------------------------------------------
20
+
21
+ export interface ChordDiagramProps {
22
+ /** Per-node scalar values (length N). Drives arc colour. */
23
+ nodeValues: number[];
24
+ /** N×N symmetric weight matrix. Drives chord colour / width. */
25
+ pairwiseWeights: number[][];
26
+ /** Node labels (length N). Falls back to "0", "1", … */
27
+ labels?: string[];
28
+ /** Indices of nodes to highlight with an outline. */
29
+ selectedIndices?: number[];
30
+ /**
31
+ * Named groups of node indices. When provided together with
32
+ * `groupSelected`, nodes belonging to each group are placed adjacent
33
+ * on the ring in group order. Each group gets a distinct outline
34
+ * colour (see `groupColors`). Takes precedence over
35
+ * `selectedIndices` for grouping / highlighting when both are given.
36
+ */
37
+ groups?: Record<string, number[]>;
38
+ /** Override outline colours for each group (cycles if fewer than groups). */
39
+ groupColors?: string[];
40
+
41
+ // --- visual knobs (all optional with sensible defaults) ---
42
+ gapDeg?: number;
43
+ radius?: number;
44
+ arcWidth?: number;
45
+ lineScale?: number | null;
46
+ /** Minimum edge weight to draw a chord. */
47
+ edgeThreshold?: number;
48
+ /** Clamp for node colour scale. */
49
+ nodeVmax?: number | null;
50
+ /** Clamp for edge colour scale. */
51
+ edgeVmax?: number | null;
52
+ title?: string | null;
53
+ width?: number;
54
+ height?: number;
55
+ selectionColor?: string;
56
+ selectionLinewidth?: number;
57
+ /** 3-stop hex colourmap for arcs. */
58
+ nodeColormap?: [string, string, string];
59
+ /** 3-stop hex colourmap for chords. */
60
+ edgeColormap?: [string, string, string];
61
+ /** Legend label for the node colour bar. */
62
+ nodeColorbarLabel?: string | null;
63
+ /** Legend label for the edge colour bar. */
64
+ edgeColorbarLabel?: string | null;
65
+ /** Prefix shown before the node value on hover (e.g. "S₁="). */
66
+ nodeHoverPrefix?: string;
67
+ /** Prefix shown before the edge value on hover (e.g. "MI="). */
68
+ edgeHoverPrefix?: string;
69
+ /**
70
+ * When `true`, reorder arcs so that selected nodes sit adjacent
71
+ * on the ring (labels still show the original names).
72
+ */
73
+ groupSelected?: boolean;
74
+ /**
75
+ * When `true` renders light text on a dark background; when `false`
76
+ * renders dark text on a transparent background. Leave `undefined`
77
+ * (the default) to inherit from the host page via `--qdk-*` CSS
78
+ * custom properties (which map VS Code / Jupyter theme vars), with
79
+ * a final fallback to `currentColor` / `transparent`.
80
+ */
81
+ darkMode?: boolean;
82
+ /**
83
+ * When `true`, interactive-only UI elements (e.g. the grouping toggle)
84
+ * are suppressed. Used during server-side SVG export.
85
+ */
86
+ static?: boolean;
87
+ /**
88
+ * Callback fired when the user toggles the grouping control.
89
+ * The host can use this to sync the new state back to a data model.
90
+ */
91
+ onGroupChange?: (grouped: boolean) => void;
92
+ }
93
+
94
+ /** Convenience alias keeping the old prop names for backward compat. */
95
+ export interface EntanglementProps {
96
+ s1Entropies: number[];
97
+ mutualInformation: number[][];
98
+ labels?: string[];
99
+ selectedIndices?: number[];
100
+ gapDeg?: number;
101
+ radius?: number;
102
+ arcWidth?: number;
103
+ lineScale?: number | null;
104
+ miThreshold?: number;
105
+ s1Vmax?: number | null;
106
+ miVmax?: number | null;
107
+ title?: string | null;
108
+ width?: number;
109
+ height?: number;
110
+ selectionColor?: string;
111
+ selectionLinewidth?: number;
112
+ nodeColormap?: [string, string, string];
113
+ edgeColormap?: [string, string, string];
114
+ groups?: Record<string, number[]>;
115
+ groupColors?: string[];
116
+ groupSelected?: boolean;
117
+ darkMode?: boolean;
118
+ static?: boolean;
119
+ onGroupChange?: (grouped: boolean) => void;
120
+ }
121
+
122
+ // ---------------------------------------------------------------------------
123
+ // Helpers
124
+ // ---------------------------------------------------------------------------
125
+
126
+ function deg2xy(deg: number, r: number): [number, number] {
127
+ const rad = (deg * Math.PI) / 180;
128
+ return [r * Math.cos(rad), r * Math.sin(rad)];
129
+ }
130
+
131
+ /** Linear interpolation between two RGB‑A colours given as [r,g,b,a]. */
132
+ type RGBA = [number, number, number, number];
133
+
134
+ function lerpColor(a: RGBA, b: RGBA, t: number): RGBA {
135
+ return [
136
+ a[0] + (b[0] - a[0]) * t,
137
+ a[1] + (b[1] - a[1]) * t,
138
+ a[2] + (b[2] - a[2]) * t,
139
+ a[3] + (b[3] - a[3]) * t,
140
+ ];
141
+ }
142
+
143
+ /** Parse "#rrggbb" to RGBA. */
144
+ function hexToRGBA(hex: string): RGBA {
145
+ const r = parseInt(hex.slice(1, 3), 16) / 255;
146
+ const g = parseInt(hex.slice(3, 5), 16) / 255;
147
+ const b = parseInt(hex.slice(5, 7), 16) / 255;
148
+ return [r, g, b, 1];
149
+ }
150
+
151
+ function rgbaToCSS(c: RGBA): string {
152
+ return `rgb(${Math.round(c[0] * 255)},${Math.round(c[1] * 255)},${Math.round(c[2] * 255)})`;
153
+ }
154
+
155
+ /** Evaluate a 3‑stop linear colour-map at position t ∈ [0,1]. */
156
+ function colormapEval(stops: [string, string, string], t: number): string {
157
+ const clamped = Math.max(0, Math.min(1, t));
158
+ const colors = stops.map(hexToRGBA) as [RGBA, RGBA, RGBA];
159
+ if (clamped <= 0.5) {
160
+ return rgbaToCSS(lerpColor(colors[0], colors[1], clamped * 2));
161
+ }
162
+ return rgbaToCSS(lerpColor(colors[1], colors[2], (clamped - 0.5) * 2));
163
+ }
164
+
165
+ const DEFAULT_NODE_CMAP: [string, string, string] = [
166
+ "#d8d8d8",
167
+ "#c82020",
168
+ "#1a1a1a",
169
+ ];
170
+ const DEFAULT_EDGE_CMAP: [string, string, string] = [
171
+ "#d8d8d8",
172
+ "#2060b0",
173
+ "#1a1a1a",
174
+ ];
175
+
176
+ /**
177
+ * Convert a CSS computed colour (e.g. "rgb(200, 32, 32)") to "#rrggbb".
178
+ * Returns the input unchanged when it already looks like a hex colour.
179
+ * Returns `null` when the value cannot be parsed.
180
+ */
181
+ function cssColorToHex(css: string): string | null {
182
+ const trimmed = css.trim();
183
+ if (/^#[0-9a-f]{6}$/i.test(trimmed)) return trimmed;
184
+ if (/^#[0-9a-f]{3}$/i.test(trimmed)) {
185
+ const [, r, g, b] = trimmed.split("");
186
+ return `#${r}${r}${g}${g}${b}${b}`;
187
+ }
188
+ const m = trimmed.match(
189
+ /^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*[\d.]+)?\s*\)$/,
190
+ );
191
+ if (!m) return null;
192
+ const hex = (n: number) => n.toString(16).padStart(2, "0");
193
+ return `#${hex(Number(m[1]))}${hex(Number(m[2]))}${hex(Number(m[3]))}`;
194
+ }
195
+
196
+ /**
197
+ * Read a 3-stop colourmap from CSS custom properties on the given element.
198
+ * Returns the fallback when the properties are absent or unparseable.
199
+ */
200
+ function readCSSColormap(
201
+ el: Element,
202
+ lo: string,
203
+ mid: string,
204
+ hi: string,
205
+ fallback: [string, string, string],
206
+ ): [string, string, string] {
207
+ const style = getComputedStyle(el);
208
+ const cLo = cssColorToHex(style.getPropertyValue(lo));
209
+ const cMid = cssColorToHex(style.getPropertyValue(mid));
210
+ const cHi = cssColorToHex(style.getPropertyValue(hi));
211
+ if (cLo && cMid && cHi) return [cLo, cMid, cHi];
212
+ return fallback;
213
+ }
214
+
215
+ /**
216
+ * Detect whether the host background is dark or light by sampling the
217
+ * computed background-color of the nearest ancestor with one.
218
+ * Returns a high-contrast colour for selection outlines.
219
+ */
220
+ function detectSelectionColor(el: Element | null): string {
221
+ if (!el || typeof getComputedStyle === "undefined") return "#FFD700";
222
+ let node: Element | null = el;
223
+ while (node) {
224
+ const bg = getComputedStyle(node).backgroundColor;
225
+ if (bg && bg !== "rgba(0, 0, 0, 0)" && bg !== "transparent") {
226
+ const m = bg.match(/\d+/g);
227
+ if (m) {
228
+ const [r, g, b] = m.map(Number);
229
+ const lum = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
230
+ // Use vivid colours that pop against the arc colourmap
231
+ return lum > 0.5 ? "#FF8C00" : "#FFD700";
232
+ }
233
+ }
234
+ node = node.parentElement;
235
+ }
236
+ return "#FFD700";
237
+ }
238
+
239
+ /** Build an SVG arc‑path for a filled annular segment. */
240
+ function arcPath(
241
+ startDeg: number,
242
+ endDeg: number,
243
+ innerR: number,
244
+ outerR: number,
245
+ ): string {
246
+ // Discretise to a polygon — simpler and avoids arc‑sweep flag headaches
247
+ // with very small or very large arcs.
248
+ const N = 80;
249
+ const pts: string[] = [];
250
+ for (let i = 0; i <= N; i++) {
251
+ const theta = ((startDeg + ((endDeg - startDeg) * i) / N) * Math.PI) / 180;
252
+ pts.push(`${outerR * Math.cos(theta)},${outerR * Math.sin(theta)}`);
253
+ }
254
+ for (let i = N; i >= 0; i--) {
255
+ const theta = ((startDeg + ((endDeg - startDeg) * i) / N) * Math.PI) / 180;
256
+ pts.push(`${innerR * Math.cos(theta)},${innerR * Math.sin(theta)}`);
257
+ }
258
+ return (
259
+ `M ${pts[0]} ` +
260
+ pts
261
+ .slice(1)
262
+ .map((p) => `L ${p}`)
263
+ .join(" ") +
264
+ " Z"
265
+ );
266
+ }
267
+
268
+ /** Cubic Bézier chord between two angles on the inner rim. */
269
+ function chordPath(
270
+ angleA: number,
271
+ angleB: number,
272
+ radius: number,
273
+ arcWidth: number,
274
+ ): string {
275
+ const inner = radius - arcWidth;
276
+ const ctrlR = inner * 0.55;
277
+ const [x0, y0] = deg2xy(angleA, inner);
278
+ const [cx0, cy0] = deg2xy(angleA, ctrlR);
279
+ const [cx1, cy1] = deg2xy(angleB, ctrlR);
280
+ const [x1, y1] = deg2xy(angleB, inner);
281
+ return `M ${x0},${y0} C ${cx0},${cy0} ${cx1},${cy1} ${x1},${y1}`;
282
+ }
283
+
284
+ // ---------------------------------------------------------------------------
285
+ // Component
286
+ // ---------------------------------------------------------------------------
287
+
288
+ // Default palette for multi-group outlines.
289
+ // Used only as a static fallback; at mount time the component generates
290
+ // a theme-aware palette for the actual number of groups.
291
+ const DEFAULT_GROUP_COLORS = ["#FFD700"];
292
+
293
+ /**
294
+ * Generate `n` maximally-spaced highlight colours.
295
+ *
296
+ * Hues are evenly distributed around the wheel with a golden-angle
297
+ * offset so that adjacent groups always contrast well, even for large n.
298
+ * Saturation and lightness adapt to the host background luminance so
299
+ * outlines remain vivid in both light and dark themes.
300
+ */
301
+ function generateGroupPalette(n: number, isDark: boolean): string[] {
302
+ if (n <= 0) return [];
303
+ const s = isDark ? 75 : 80; // saturation %
304
+ const l = isDark ? 65 : 48; // lightness %
305
+ const goldenAngle = 137.508; // degrees – spreads hues well
306
+ const startHue = 48; // start near gold
307
+ const palette: string[] = [];
308
+ for (let i = 0; i < n; i++) {
309
+ const h = (startHue + i * goldenAngle) % 360;
310
+ palette.push(`hsl(${h.toFixed(1)},${s}%,${l}%)`);
311
+ }
312
+ return palette;
313
+ }
314
+
315
+ /**
316
+ * Measure whether the host background is dark by walking up the DOM.
317
+ * Returns true for dark backgrounds, false for light (or unknown).
318
+ */
319
+ function detectIsDark(el: Element | null): boolean {
320
+ if (!el || typeof getComputedStyle === "undefined") return false;
321
+ let node: Element | null = el;
322
+ while (node) {
323
+ const bg = getComputedStyle(node).backgroundColor;
324
+ if (bg && bg !== "rgba(0, 0, 0, 0)" && bg !== "transparent") {
325
+ const m = bg.match(/\d+/g);
326
+ if (m) {
327
+ const lum =
328
+ (0.299 * Number(m[0]) + 0.587 * Number(m[1]) + 0.114 * Number(m[2])) /
329
+ 255;
330
+ return lum <= 0.5;
331
+ }
332
+ }
333
+ node = node.parentElement;
334
+ }
335
+ return false;
336
+ }
337
+
338
+ export function ChordDiagram(props: ChordDiagramProps) {
339
+ const {
340
+ nodeValues,
341
+ pairwiseWeights,
342
+ labels: labelsProp,
343
+ selectedIndices,
344
+ groups,
345
+ groupColors: groupColorsProp,
346
+ gapDeg = 3,
347
+ radius = 1,
348
+ arcWidth = 0.08,
349
+ lineScale: lineScaleProp = null,
350
+ edgeThreshold = 0,
351
+ nodeVmax = null,
352
+ edgeVmax = null,
353
+ title = null,
354
+ width = 600,
355
+ height = 660,
356
+ selectionColor: selectionColorProp,
357
+ selectionLinewidth = 1.2,
358
+ nodeColormap = DEFAULT_NODE_CMAP,
359
+ edgeColormap = DEFAULT_EDGE_CMAP,
360
+ nodeColorbarLabel = null,
361
+ edgeColorbarLabel = null,
362
+ nodeHoverPrefix = "",
363
+ edgeHoverPrefix = "",
364
+ groupSelected = false,
365
+ darkMode,
366
+ static: isStatic = false,
367
+ onGroupChange,
368
+ } = props;
369
+
370
+ // --- theme-resolved colours ---
371
+ // When darkMode is undefined the component inherits from the host
372
+ // environment via --qdk-* CSS custom properties (set by qdk-theme.css
373
+ // which maps VS Code / Jupyter / OS theme vars). The final fallback
374
+ // is `currentColor` / `transparent` for plain-browser contexts.
375
+ // When darkMode is explicitly true/false, concrete hex values are
376
+ // used so exported SVGs are fully self-contained.
377
+ const FONT_FAMILY_FALLBACK =
378
+ '"Segoe UI", Roboto, Helvetica, Arial, sans-serif';
379
+ const hasExplicitTheme = darkMode !== undefined;
380
+ const fontFamily = hasExplicitTheme
381
+ ? FONT_FAMILY_FALLBACK
382
+ : `var(--qdk-font-family, ${FONT_FAMILY_FALLBACK})`;
383
+ const textColor = hasExplicitTheme
384
+ ? darkMode
385
+ ? "#e0e0e0"
386
+ : "#222222"
387
+ : "var(--qdk-host-foreground, currentColor)";
388
+ const bgColor = hasExplicitTheme
389
+ ? darkMode
390
+ ? "#1e1e1e"
391
+ : "transparent"
392
+ : "var(--qdk-host-background, transparent)";
393
+ const mutedColor = hasExplicitTheme
394
+ ? darkMode
395
+ ? "#666666"
396
+ : "#aaaaaa"
397
+ : "var(--qdk-foreground-muted, #aaaaaa)";
398
+ const midGray = hasExplicitTheme ? "#888888" : "var(--qdk-mid-gray, #888888)";
399
+
400
+ const n = nodeValues.length;
401
+
402
+ // --- hover state ---
403
+ const [hoveredIdx, setHoveredIdx] = useState<number | null>(null);
404
+
405
+ // --- grouping toggle (only relevant when there is a selection) ---
406
+ // Build the canonical group list: either from `groups` or the legacy
407
+ // `selectedIndices` (treated as a single unnamed group).
408
+ const groupEntries: [string, number[]][] = [];
409
+ if (groups && Object.keys(groups).length > 0) {
410
+ for (const [name, indices] of Object.entries(groups)) {
411
+ groupEntries.push([name, indices]);
412
+ }
413
+ } else if (selectedIndices && selectedIndices.length > 0) {
414
+ groupEntries.push(["selected", selectedIndices]);
415
+ }
416
+
417
+ // Map from orbital index → outline colour for that group.
418
+ // The palette is generated at mount to match the number of groups and
419
+ // the host theme. Before mount, fall back to DEFAULT_GROUP_COLORS.
420
+ const [autoPalette, setAutoPalette] =
421
+ useState<string[]>(DEFAULT_GROUP_COLORS);
422
+ const palette = groupColorsProp ?? autoPalette;
423
+ const nodeGroupColor = new Map<number, string>();
424
+ {
425
+ let colorIdx = 0;
426
+ for (const [, indices] of groupEntries) {
427
+ const color =
428
+ groupEntries.length === 1
429
+ ? (selectionColorProp ?? palette[0])
430
+ : palette[colorIdx % palette.length];
431
+ for (const idx of indices) {
432
+ nodeGroupColor.set(idx, color);
433
+ }
434
+ colorIdx++;
435
+ }
436
+ }
437
+
438
+ const hasSelection = nodeGroupColor.size > 0;
439
+ const [isGrouped, setIsGrouped] = useState(groupSelected);
440
+ // Sync if the prop changes externally
441
+ useEffect(() => setIsGrouped(groupSelected), [groupSelected]);
442
+
443
+ // --- background-aware selection colour ---
444
+ const svgRef = useRef<SVGSVGElement>(null);
445
+ const [autoSelectionColor, setAutoSelectionColor] = useState("#FFD700");
446
+ // --- CSS-resolved colormaps (read from --qdk-chord-* custom props) ---
447
+ const [resolvedNodeCmap, setResolvedNodeCmap] =
448
+ useState<[string, string, string]>(nodeColormap);
449
+ const [resolvedEdgeCmap, setResolvedEdgeCmap] =
450
+ useState<[string, string, string]>(edgeColormap);
451
+ useEffect(() => {
452
+ if (svgRef.current) {
453
+ const isDark = detectIsDark(svgRef.current);
454
+ setAutoSelectionColor(detectSelectionColor(svgRef.current));
455
+ // Generate a theme-aware palette sized to the actual group count.
456
+ if (!groupColorsProp) {
457
+ const nGroups = Math.max(groupEntries.length, 1);
458
+ setAutoPalette(generateGroupPalette(nGroups, isDark));
459
+ }
460
+ if (!hasExplicitTheme) {
461
+ setResolvedNodeCmap(
462
+ readCSSColormap(
463
+ svgRef.current,
464
+ "--qdk-chord-node-lo",
465
+ "--qdk-chord-node-mid",
466
+ "--qdk-chord-node-hi",
467
+ nodeColormap,
468
+ ),
469
+ );
470
+ setResolvedEdgeCmap(
471
+ readCSSColormap(
472
+ svgRef.current,
473
+ "--qdk-chord-edge-lo",
474
+ "--qdk-chord-edge-mid",
475
+ "--qdk-chord-edge-hi",
476
+ edgeColormap,
477
+ ),
478
+ );
479
+ }
480
+ }
481
+ }, []);
482
+ const selectionColor = selectionColorProp ?? autoSelectionColor;
483
+ // Use prop colormaps when explicit darkMode is set; otherwise prefer
484
+ // CSS-resolved values which respect the host theme.
485
+ const activeNodeCmap = hasExplicitTheme ? nodeColormap : resolvedNodeCmap;
486
+ const activeEdgeCmap = hasExplicitTheme ? edgeColormap : resolvedEdgeCmap;
487
+
488
+ // --- labels ---
489
+ const labels: string[] =
490
+ labelsProp && labelsProp.length === n
491
+ ? labelsProp
492
+ : Array.from({ length: n }, (_, i) => String(i));
493
+
494
+ // --- colour scales ---
495
+ const nodeMax = nodeVmax ?? Math.max(...nodeValues, 1);
496
+ const edgeMax =
497
+ edgeVmax ?? Math.max(...pairwiseWeights.flatMap((row) => row), 1);
498
+
499
+ const arcColours = nodeValues.map((v) =>
500
+ colormapEval(activeNodeCmap, v / nodeMax),
501
+ );
502
+
503
+ // --- line scale ---
504
+ const maxLw = Math.max(12 * (20 / Math.max(n, 1)) ** 0.5, 2);
505
+ let lineScale: number;
506
+ {
507
+ let peak = 0;
508
+ for (let i = 0; i < n; i++)
509
+ for (let j = 0; j < n; j++) peak = Math.max(peak, pairwiseWeights[i][j]);
510
+ if (peak <= 0) peak = 1;
511
+ lineScale =
512
+ lineScaleProp !== null ? lineScaleProp : maxLw / Math.sqrt(peak);
513
+ }
514
+
515
+ // --- arc geometry ---
516
+ const totals = nodeValues.slice();
517
+ let grand = totals.reduce((a, b) => a + b, 0);
518
+ if (grand === 0) {
519
+ totals.fill(1);
520
+ grand = n;
521
+ }
522
+ const gapTotal = gapDeg * n;
523
+ const arcDegs = totals.map((t) => ((360 - gapTotal) * t) / grand);
524
+
525
+ // --- ring ordering (group nodes together when requested) ---
526
+ const order: number[] = Array.from({ length: n }, (_, i) => i);
527
+ if (isGrouped && groupEntries.length > 0) {
528
+ const grouped: number[] = [];
529
+ const groupedSet = new Set<number>();
530
+ for (const [, indices] of groupEntries) {
531
+ for (const idx of indices) {
532
+ if (!groupedSet.has(idx)) {
533
+ grouped.push(idx);
534
+ groupedSet.add(idx);
535
+ }
536
+ }
537
+ }
538
+ const ungrouped: number[] = [];
539
+ for (let i = 0; i < n; i++) {
540
+ if (!groupedSet.has(i)) ungrouped.push(i);
541
+ }
542
+ order.length = 0;
543
+ order.push(...grouped, ...ungrouped);
544
+ }
545
+
546
+ const starts: number[] = new Array(n);
547
+ starts[order[0]] = 0;
548
+ for (let p = 1; p < n; p++) {
549
+ const prev = order[p - 1];
550
+ const curr = order[p];
551
+ starts[curr] = starts[prev] + arcDegs[prev] + gapDeg;
552
+ }
553
+
554
+ const arcMids = starts.map((s, i) => s + arcDegs[i] / 2);
555
+
556
+ // --- label tiers (avoid overlapping) ---
557
+ const labelFontSize = n <= 20 ? 13.5 : 10.5;
558
+ const maxLabelLen = Math.max(...labels.map((l) => l.length));
559
+ const charDeg = (labelFontSize * 0.7 * maxLabelLen) / Math.max(radius, 0.5);
560
+ const minSepDeg = charDeg * 0.8;
561
+ const baseOffset = 0.07;
562
+ const tierStep = 0.09;
563
+ const maxTiers = 4;
564
+
565
+ const indexOrder = Array.from({ length: n }, (_, i) => i).sort(
566
+ (a, b) => arcMids[a] - arcMids[b],
567
+ );
568
+ const tier = new Array(n).fill(0);
569
+ let prevAngle = -999;
570
+ let prevTier = -1;
571
+ for (const idx of indexOrder) {
572
+ const ang = arcMids[idx];
573
+ if (ang - prevAngle < minSepDeg) {
574
+ tier[idx] = (prevTier + 1) % maxTiers;
575
+ } else {
576
+ tier[idx] = 0;
577
+ }
578
+ prevAngle = ang;
579
+ prevTier = tier[idx];
580
+ }
581
+ // wrap‑around
582
+ const firstIdx = indexOrder[0];
583
+ const lastIdx = indexOrder[indexOrder.length - 1];
584
+ const wrapGap = arcMids[firstIdx] + 360 - arcMids[lastIdx];
585
+ if (wrapGap < minSepDeg && tier[firstIdx] === tier[lastIdx]) {
586
+ tier[firstIdx] = (tier[lastIdx] + 1) % maxTiers;
587
+ }
588
+
589
+ // --- chord computation ---
590
+ const rowSums = pairwiseWeights.map((row) => row.reduce((a, b) => a + b, 0));
591
+
592
+ type Conn = { j: number; val: number };
593
+ const nodeConns: Conn[][] = Array.from({ length: n }, () => []);
594
+ for (let i = 0; i < n; i++) {
595
+ for (let j = 0; j < n; j++) {
596
+ if (i === j) continue;
597
+ const val = pairwiseWeights[i][j];
598
+ if (val <= edgeThreshold) continue;
599
+ nodeConns[i].push({ j, val });
600
+ }
601
+ const mid = arcMids[i];
602
+ nodeConns[i].sort(
603
+ (a, b) =>
604
+ ((mid - arcMids[a.j] + 360) % 360) - ((mid - arcMids[b.j] + 360) % 360),
605
+ );
606
+ }
607
+
608
+ const cursor = starts.slice();
609
+ const allocated = new Map<string, number>();
610
+ for (let i = 0; i < n; i++) {
611
+ for (const { j, val } of nodeConns[i]) {
612
+ const span = rowSums[i] > 0 ? (arcDegs[i] * val) / rowSums[i] : 0;
613
+ allocated.set(`${i},${j}`, cursor[i] + span / 2);
614
+ cursor[i] += span;
615
+ }
616
+ }
617
+
618
+ type Chord = {
619
+ i: number;
620
+ j: number;
621
+ val: number;
622
+ angleI: number;
623
+ angleJ: number;
624
+ };
625
+ const chords: Chord[] = [];
626
+ for (let i = 0; i < n; i++) {
627
+ for (let j = i + 1; j < n; j++) {
628
+ const keyIJ = `${i},${j}`;
629
+ const keyJI = `${j},${i}`;
630
+ if (!allocated.has(keyIJ)) continue;
631
+ chords.push({
632
+ i,
633
+ j,
634
+ val: pairwiseWeights[i][j],
635
+ angleI: allocated.get(keyIJ)!,
636
+ angleJ: allocated.get(keyJI)!,
637
+ });
638
+ }
639
+ }
640
+ // lightest first so darkest draws on top
641
+ chords.sort((a, b) => a.val - b.val);
642
+
643
+ // --- hover: partition chords ---
644
+ const isHovering = hoveredIdx !== null;
645
+ const bgChords: Chord[] = [];
646
+ const fgChords: Chord[] = [];
647
+ const connectedSet = new Set<number>();
648
+ if (isHovering) {
649
+ for (const ch of chords) {
650
+ if (ch.i === hoveredIdx || ch.j === hoveredIdx) {
651
+ fgChords.push(ch);
652
+ connectedSet.add(ch.i);
653
+ connectedSet.add(ch.j);
654
+ } else {
655
+ bgChords.push(ch);
656
+ }
657
+ }
658
+ }
659
+
660
+ // --- viewBox ---
661
+ const maxOffset = baseOffset + Math.max(0, ...tier) * tierStep + 0.15;
662
+ const lim = radius + maxOffset;
663
+ // Map [-lim, lim] to [0, width/height] — compact legend area
664
+ const titleH = 50; // px reserved for title at top
665
+ const hasNodeBar = !!nodeColorbarLabel;
666
+ const hasEdgeBar = !!edgeColorbarLabel;
667
+ const legendH =
668
+ hasNodeBar || hasEdgeBar ? (hasNodeBar && hasEdgeBar ? 180 : 100) : 0;
669
+ const diagramH = height - legendH - titleH;
670
+ const vbPad = lim * 0.04;
671
+ const vbSize = (lim + vbPad) * 2;
672
+ const scale = Math.min(width, diagramH) / vbSize;
673
+
674
+ // Colour-bar dimensions (drawn inside the SVG, close to diagram)
675
+ const cbGap = 40; // px between diagram bottom and first bar
676
+ const cbY = titleH + diagramH + cbGap;
677
+ const cbW = width * 0.6;
678
+ const cbX = (width - cbW) / 2;
679
+ const cbH = 10;
680
+ const cbSpacing = 68; // vertical distance between the two bars (label + bar + ticks)
681
+ const numCbStops = 64;
682
+ const numTicks = 5; // tick count on each colour bar
683
+
684
+ return (
685
+ <svg
686
+ xmlns="http://www.w3.org/2000/svg"
687
+ width={width}
688
+ height={height}
689
+ class="qs-chord-diagram"
690
+ style={{ background: bgColor, fontFamily: fontFamily }}
691
+ ref={svgRef}
692
+ >
693
+ {/* Title */}
694
+ {title && (
695
+ <text
696
+ x={width / 2}
697
+ y={28}
698
+ text-anchor="middle"
699
+ font-size="21"
700
+ font-weight="bold"
701
+ fill={textColor}
702
+ >
703
+ {title}
704
+ </text>
705
+ )}
706
+
707
+ {/* Group-selected toggle (only when there is a selection; hidden in static SVG export) */}
708
+ {hasSelection && !isStatic && (
709
+ <g
710
+ class="oe-group-toggle"
711
+ transform={`translate(${width - 14}, 14)`}
712
+ style={{ cursor: "pointer" }}
713
+ onClick={() => {
714
+ setIsGrouped((v) => {
715
+ const next = !v;
716
+ onGroupChange?.(next);
717
+ return next;
718
+ });
719
+ }}
720
+ >
721
+ <title>
722
+ {isGrouped
723
+ ? "Ungroup selected items"
724
+ : "Group selected items together"}
725
+ </title>
726
+ <rect
727
+ x={-80}
728
+ y={-10}
729
+ width={80}
730
+ height={20}
731
+ rx={10}
732
+ fill={isGrouped ? selectionColor : midGray}
733
+ opacity={0.85}
734
+ />
735
+ <circle cx={isGrouped ? -10 : -70} cy={0} r={7} fill="white" />
736
+ <text
737
+ x={isGrouped ? -52 : -33}
738
+ y={0}
739
+ text-anchor="middle"
740
+ dominant-baseline="central"
741
+ font-size="10"
742
+ font-weight="bold"
743
+ fill="white"
744
+ >
745
+ {isGrouped ? "Grouped" : "Ungrouped"}
746
+ </text>
747
+ </g>
748
+ )}
749
+
750
+ {/* Diagram group — centred and scaled to fit */}
751
+ <g
752
+ transform={`translate(${width / 2},${titleH + diagramH / 2}) scale(${scale})`}
753
+ >
754
+ {/* Chord lines — when hovering, split into dimmed background + bright foreground */}
755
+ {(isHovering ? bgChords : chords).map((ch, ci) => {
756
+ const c = colormapEval(activeEdgeCmap, ch.val / edgeMax);
757
+ const lwPx = Math.min(Math.sqrt(ch.val) * lineScale, maxLw);
758
+ const lw = lwPx / scale;
759
+ return (
760
+ <path
761
+ key={`chord-bg-${ci}`}
762
+ d={chordPath(ch.angleI, ch.angleJ, radius, arcWidth)}
763
+ fill="none"
764
+ stroke={c}
765
+ stroke-width={lw}
766
+ stroke-linecap="round"
767
+ opacity={isHovering ? 0.12 : 1}
768
+ />
769
+ );
770
+ })}
771
+ {/* Highlighted chords for hovered orbital (drawn on top) */}
772
+ {fgChords.map((ch, ci) => {
773
+ const c = colormapEval(activeEdgeCmap, ch.val / edgeMax);
774
+ const lwPx = Math.min(Math.sqrt(ch.val) * lineScale, maxLw);
775
+ const lw = lwPx / scale;
776
+ return (
777
+ <path
778
+ key={`chord-fg-${ci}`}
779
+ d={chordPath(ch.angleI, ch.angleJ, radius, arcWidth)}
780
+ fill="none"
781
+ stroke={c}
782
+ stroke-width={Math.max(lw, 1.5 / scale)}
783
+ stroke-linecap="round"
784
+ opacity={1}
785
+ />
786
+ );
787
+ })}
788
+
789
+ {/* Arcs */}
790
+ {Array.from({ length: n }, (_, i) => (
791
+ <path
792
+ key={`arc-${i}`}
793
+ d={arcPath(
794
+ starts[i],
795
+ starts[i] + arcDegs[i],
796
+ radius - arcWidth,
797
+ radius,
798
+ )}
799
+ fill={arcColours[i]}
800
+ opacity={isHovering && !connectedSet.has(i) ? 0.25 : 1}
801
+ onMouseEnter={() => setHoveredIdx(i)}
802
+ onMouseLeave={() => setHoveredIdx(null)}
803
+ style={{ cursor: "pointer" }}
804
+ />
805
+ ))}
806
+
807
+ {/* Selection outlines */}
808
+ {Array.from({ length: n }, (_, i) => {
809
+ const gc = nodeGroupColor.get(i);
810
+ return gc ? (
811
+ <path
812
+ key={`sel-${i}`}
813
+ d={arcPath(
814
+ starts[i],
815
+ starts[i] + arcDegs[i],
816
+ radius - arcWidth,
817
+ radius,
818
+ )}
819
+ fill="none"
820
+ stroke={gc}
821
+ stroke-width={selectionLinewidth / scale}
822
+ style={{ pointerEvents: "none" }}
823
+ />
824
+ ) : null;
825
+ })}
826
+
827
+ {/* Labels & tick lines */}
828
+ {Array.from({ length: n }, (_, i) => {
829
+ const mid = arcMids[i];
830
+ const t = tier[i];
831
+ const offset = baseOffset + t * tierStep;
832
+ const [lx, ly] = deg2xy(mid, radius + offset);
833
+ const angle = mid % 360;
834
+ const ha = angle > 90 && angle < 270 ? "end" : "start";
835
+ const rot = angle > 90 && angle < 270 ? angle - 180 : angle;
836
+
837
+ const tickLine =
838
+ t > 0
839
+ ? (() => {
840
+ const [rx, ry] = deg2xy(mid, radius + 0.01);
841
+ return (
842
+ <line
843
+ x1={rx}
844
+ y1={ry}
845
+ x2={lx}
846
+ y2={ly}
847
+ stroke={mutedColor}
848
+ stroke-width={0.5 / scale}
849
+ />
850
+ );
851
+ })()
852
+ : null;
853
+
854
+ // Font size in SVG user units — we're in a scaled group so
855
+ // approximate by dividing the pt size by the scale factor.
856
+ const fsPx = labelFontSize / scale;
857
+
858
+ // When hovering, replace the plain label with value info
859
+ const isThisHovered = hoveredIdx === i;
860
+ const isConnected = connectedSet.has(i);
861
+ let labelText = labels[i];
862
+ let labelOpacity = 1;
863
+ if (isHovering) {
864
+ if (isThisHovered) {
865
+ labelText = `${labels[i]} ${nodeHoverPrefix}${nodeValues[i].toFixed(3)}`;
866
+ } else if (isConnected && hoveredIdx !== null) {
867
+ labelText = `${labels[i]} ${edgeHoverPrefix}${pairwiseWeights[hoveredIdx][i].toFixed(3)}`;
868
+ } else {
869
+ labelOpacity = 0.15;
870
+ }
871
+ }
872
+
873
+ return (
874
+ <g key={`label-${i}`} opacity={labelOpacity}>
875
+ {tickLine}
876
+ <text
877
+ x={lx}
878
+ y={ly}
879
+ text-anchor={ha}
880
+ dominant-baseline="central"
881
+ font-size={fsPx}
882
+ font-weight="bold"
883
+ fill={textColor}
884
+ transform={`rotate(${rot},${lx},${ly})`}
885
+ >
886
+ {labelText}
887
+ </text>
888
+ </g>
889
+ );
890
+ })}
891
+ </g>
892
+
893
+ {/* ---- Colour-bar legends ---- */}
894
+ {/* Node value colour bar */}
895
+ {nodeColorbarLabel && (
896
+ <g>
897
+ <text
898
+ x={width / 2}
899
+ y={cbY - 6}
900
+ text-anchor="middle"
901
+ font-size="18"
902
+ fill={textColor}
903
+ >
904
+ {nodeColorbarLabel}
905
+ </text>
906
+ {Array.from({ length: numCbStops }, (_, k) => {
907
+ const t = k / (numCbStops - 1);
908
+ return (
909
+ <rect
910
+ key={`cb-arc-${k}`}
911
+ x={cbX + (cbW * k) / numCbStops}
912
+ y={cbY}
913
+ width={cbW / numCbStops + 0.5}
914
+ height={cbH}
915
+ fill={colormapEval(activeNodeCmap, t)}
916
+ />
917
+ );
918
+ })}
919
+ {/* Ticks */}
920
+ {Array.from({ length: numTicks }, (_, k) => {
921
+ const frac = k / (numTicks - 1);
922
+ const xPos = cbX + cbW * frac;
923
+ const val = nodeMax * frac;
924
+ return (
925
+ <g key={`cb-arc-tick-${k}`}>
926
+ <line
927
+ x1={xPos}
928
+ y1={cbY + cbH}
929
+ x2={xPos}
930
+ y2={cbY + cbH + 3}
931
+ stroke={textColor}
932
+ stroke-width={0.5}
933
+ />
934
+ <text
935
+ x={xPos}
936
+ y={cbY + cbH + 14}
937
+ text-anchor="middle"
938
+ font-size="14"
939
+ fill={textColor}
940
+ >
941
+ {val.toFixed(2)}
942
+ </text>
943
+ </g>
944
+ );
945
+ })}
946
+ </g>
947
+ )}
948
+
949
+ {/* Edge weight colour bar */}
950
+ {edgeColorbarLabel && (
951
+ <g>
952
+ <text
953
+ x={width / 2}
954
+ y={cbY + cbH + cbSpacing - 6}
955
+ text-anchor="middle"
956
+ font-size="18"
957
+ fill={textColor}
958
+ >
959
+ {edgeColorbarLabel}
960
+ </text>
961
+ {Array.from({ length: numCbStops }, (_, k) => {
962
+ const t = k / (numCbStops - 1);
963
+ return (
964
+ <rect
965
+ key={`cb-mi-${k}`}
966
+ x={cbX + (cbW * k) / numCbStops}
967
+ y={cbY + cbH + cbSpacing}
968
+ width={cbW / numCbStops + 0.5}
969
+ height={cbH}
970
+ fill={colormapEval(activeEdgeCmap, t)}
971
+ />
972
+ );
973
+ })}
974
+ {/* Ticks */}
975
+ {Array.from({ length: numTicks }, (_, k) => {
976
+ const frac = k / (numTicks - 1);
977
+ const xPos = cbX + cbW * frac;
978
+ const val = edgeMax * frac;
979
+ return (
980
+ <g key={`cb-mi-tick-${k}`}>
981
+ <line
982
+ x1={xPos}
983
+ y1={cbY + cbH + cbSpacing + cbH}
984
+ x2={xPos}
985
+ y2={cbY + cbH + cbSpacing + cbH + 3}
986
+ stroke={textColor}
987
+ stroke-width={0.5}
988
+ />
989
+ <text
990
+ x={xPos}
991
+ y={cbY + cbH + cbSpacing + cbH + 14}
992
+ text-anchor="middle"
993
+ font-size="14"
994
+ fill={textColor}
995
+ >
996
+ {val.toFixed(2)}
997
+ </text>
998
+ </g>
999
+ );
1000
+ })}
1001
+ </g>
1002
+ )}
1003
+ </svg>
1004
+ );
1005
+ }
1006
+
1007
+ // ---------------------------------------------------------------------------
1008
+ // Orbital Entanglement — convenience wrapper
1009
+ // ---------------------------------------------------------------------------
1010
+
1011
+ /**
1012
+ * Orbital entanglement chord diagram.
1013
+ *
1014
+ * Thin wrapper around `ChordDiagram` that accepts `s1Entropies` /
1015
+ * `mutualInformation` and supplies orbital-specific defaults for the
1016
+ * title, legend labels, colormaps, and scale maxima.
1017
+ */
1018
+ export function Entanglement(props: EntanglementProps) {
1019
+ const {
1020
+ s1Entropies,
1021
+ mutualInformation,
1022
+ miThreshold,
1023
+ s1Vmax,
1024
+ miVmax,
1025
+ title = "Entanglement",
1026
+ ...rest
1027
+ } = props;
1028
+
1029
+ return (
1030
+ <ChordDiagram
1031
+ nodeValues={s1Entropies}
1032
+ pairwiseWeights={mutualInformation}
1033
+ edgeThreshold={miThreshold}
1034
+ nodeVmax={s1Vmax ?? Math.log(4)}
1035
+ edgeVmax={miVmax ?? Math.log(16)}
1036
+ nodeColorbarLabel="Single-orbital entropy"
1037
+ edgeColorbarLabel="Mutual information"
1038
+ nodeHoverPrefix="S\u2081="
1039
+ edgeHoverPrefix="MI="
1040
+ title={title}
1041
+ {...rest}
1042
+ />
1043
+ );
1044
+ }