modules/fonctionsMaths.js

  1. import { number, add, unequal, largerEq, fraction, equal, multiply, inv, matrix, max, polynomialRoot, round, acos, abs } from 'mathjs'
  2. import FractionX from './FractionEtendue.js'
  3. import { calcul, arrondi, ecritureAlgebrique, egal, randint, rienSi1, ecritureAlgebriqueSauf1, choice } from './outils.js'
  4. /**
  5. * Convertit un angle de radian vers degrés et fonction inverse
  6. * @Example
  7. * // PI->180
  8. * @author Jean-Claude Lhote
  9. */
  10. export function degres (radians) {
  11. return radians * 180 / Math.PI
  12. }
  13. export function radians (degres) {
  14. return degres * Math.PI / 180
  15. }
  16. /**
  17. * @param {number} a angle en degrés
  18. * @returns flottant : le cosinus de l'angle
  19. * @author Jean-Claude Lhote
  20. */
  21. export function degCos (a) {
  22. return calcul(Math.cos(radians(a)))
  23. }
  24. /**
  25. * @param {number} a angle en degrés
  26. * @returns flottant : le sinus de l'angle
  27. * @author Jean-Claude Lhote
  28. */
  29. export function degSin (a) {
  30. return calcul(Math.sin(radians(a)))
  31. }
  32. /**
  33. * @param {number} a angle en degrés
  34. * @returns flottant : la tangente de l'angle
  35. * @author Jean-Claude Lhote
  36. */
  37. export function degTan (a) {
  38. return calcul(Math.tan(radians(a)))
  39. }
  40. /**
  41. * @param {number} un nombre qui correspond au cosinus de l'angle
  42. * @returns flottant : la mesure de l'angle en degrés
  43. * @author Jean-Claude Lhote
  44. */
  45. export function degAcos (x) {
  46. return arrondi(degres(Math.acos(x)), 1)
  47. }
  48. /**
  49. * @param {number} un nombre qui correspond au sinus de l'angle
  50. * @returns flottant : la mesure de l'angle en degrés
  51. * @author Jean-Claude Lhote
  52. */
  53. export function degAsin (x) {
  54. return arrondi(degres(Math.asin(x)), 1)
  55. }
  56. /**
  57. * @param {number} un nombre qui correspond à la tangente de l'angle
  58. * @returns flottant : la mesure de l'angle en degrés
  59. * @author Jean-Claude Lhote
  60. */
  61. export function degAtan (x) {
  62. return arrondi(degres(Math.atan(x)), 1)
  63. }
  64. /**
  65. * retourne un décimal sans décimales bizarres
  66. * Déprécié -> utiliser la classe Decimal de Decimal.js pour travailler avec de vrais décimaux
  67. * @author Jean-Claude Lhote
  68. */
  69. export function calcule (a, arrondir = false) {
  70. if (!arrondir) {
  71. if (typeof a === 'string') {
  72. return parseFloat(Number(a).toFixed(6))
  73. } else {
  74. return parseFloat(a.toFixed(6))
  75. }
  76. } else {
  77. if (typeof a === 'string') {
  78. return parseFloat(Number(a).toFixed(arrondir))
  79. } else {
  80. return parseFloat(a.toFixed(arrondir))
  81. }
  82. }
  83. }
  84. /**
  85. * retourne une chaine contenant le résultat du calcul avec la virgule comme séparateur et pas de décimales bizarres
  86. * déprécié -> utiliser Decimal.toString() pour travailler avec les décimaux
  87. * @author Jean-Claude Lhote
  88. */
  89. export function calculeS (a, arrondir = false) {
  90. let result
  91. if (!arrondir) {
  92. if (typeof a === 'string') {
  93. result = Number(a).toFixed(6).split('.')
  94. } else {
  95. result = a.toFixed(6).split('.')
  96. }
  97. } else {
  98. if (typeof a === 'string') {
  99. result = Number(a).toFixed(arrondir).split('.')
  100. } else {
  101. result = a.toFixed(arrondir).split('.')
  102. }
  103. }
  104. result[1] = result[1].replace(/[0]*$/, '')
  105. if (result[1].length !== 0) { return result[0] + ',' + result[1] } else return result[0]
  106. }
  107. export function fractionLatexToMathjs (fractionLatex) {
  108. const parts = fractionLatex.split('{')
  109. const num = Number(parts[1].substring(0, parts[1].length - 1))
  110. const den = Number(parts[2].substring(0, parts[2].length - 1))
  111. return new FractionX(num, den)
  112. }
  113. /**
  114. * delta(true) retourne dans un tableau des valeurs de a, b, c telles que b*b-4*a*c >0
  115. * delta(false) retourne dans un tableau des valeurs de a, b, c telles que b*b-4*a*c <0
  116. * @author Jean-Claude Lhote
  117. */
  118. export function choisiDelta (positif) {
  119. let d, a, b, c
  120. do {
  121. a = randint(-5, 5, 0)
  122. b = randint(-5, 5, 0)
  123. c = randint(-5, 5, 0)
  124. d = b * b - 4 * a * c
  125. } while (positif ? d <= 0 : d >= 0)
  126. return [a, b, c]
  127. }
  128. /**
  129. * onction qui retourne un polynome du second degré correctement écrit.
  130. * @param {number} a
  131. * @param {number} b
  132. * @param {number} c
  133. * @returns {string}
  134. */
  135. export function expTrinome (a, b, c) {
  136. let expr = ''
  137. if (typeof a === 'number') {
  138. switch (a) {
  139. case 0:
  140. break
  141. case -1:
  142. expr += '-x^2'
  143. break
  144. case 1:
  145. expr += 'x^2'
  146. break
  147. default:
  148. expr += `${a}x^2`
  149. break
  150. }
  151. } else {
  152. expr += `${a}x^2`
  153. }
  154. if (typeof b === 'number') {
  155. switch (b) {
  156. case 0:
  157. break
  158. case -1:
  159. expr += '-x'
  160. break
  161. case 1:
  162. expr += '+x'
  163. break
  164. default:
  165. if (a === 0) {
  166. expr += `${b}`
  167. } else expr += `${ecritureAlgebrique(b)}x`
  168. break
  169. }
  170. } else {
  171. if (a === 0) {
  172. expr += `${b}x`
  173. } else {
  174. expr += `+${b}x`
  175. }
  176. }
  177. if (typeof c === 'number') {
  178. if (a === 0 && b === 0) {
  179. expr += `${c}`
  180. } else {
  181. if (c !== 0) {
  182. expr += `${ecritureAlgebrique(c)}`
  183. }
  184. }
  185. } else {
  186. expr += `+${c}`
  187. }
  188. return expr
  189. }
  190. /**
  191. * inspiré de l'article 'Algorithme de calcul d'une courbe spline de lissage'
  192. * de cet article 'spline' de wikipedia : https://fr.wikipedia.org/wiki/Spline
  193. * ça fonctionne quand les noeuds sont 'bien répartis'... ce qui n'est pas évident...
  194. * Les noeuds sont des couples (x,y)
  195. * On lui préférera la Spline de Catmull-Rom ci-dessous.
  196. * Adaptation pour Mathalea
  197. * @author Jean-Claude Lhote
  198. */
  199. class Spline {
  200. constructor (tabNoeuds) {
  201. const x = []
  202. const y = []
  203. const h = []
  204. const F = matrix()
  205. const R = matrix()
  206. let n
  207. for (let i = 0; i < tabNoeuds.length; i++) {
  208. x[i] = tabNoeuds[i][0]
  209. y[i] = tabNoeuds[i][1]
  210. }
  211. if (trieCouples(x, y)) { // On mets les couples (x,y) dans l'ordre des x croissants.
  212. n = x.length
  213. } else {
  214. console.log('il y a un problème avec ce tableau des noeuds')
  215. return false
  216. }
  217. for (let i = 0; i < n; i++) {
  218. console.log('(', x[i], ';', y[i], ')')
  219. }
  220. for (let i = 0; i < n - 1; i++) {
  221. h[i] = x[i + 1] - x[i] // on calcule les amplitudes des intervalles.
  222. }
  223. console.log(' Les intervalles :\n ', h)
  224. F.resize([n], 0)
  225. for (let i = 2; i < n; i++) { // On construit la matrice des dérivées secondes
  226. F._data[i - 1] = (y[i] - y[i - 1]) / h[i - 1] - (y[i - 1] - y[i - 2]) / h[i - 2]
  227. }
  228. console.log('La matrice des dérivéées secondes :\n', F)
  229. R.resize([n, n], 0)
  230. for (let i = 1; i <= n; i++) { // On construit la matrice carrée de calcul
  231. if (i === 1) {
  232. R._data[0][0] = 1 // seul le premier élément de la première ligne n'est pas nul
  233. } else if (i === n) {
  234. R._data[n - 1][n - 1] = 1 // seul le dernier élément de la dernière ligne n'est pas nul
  235. } else { // on Construit les diagonales n = 2 .. n-1
  236. R._data[i - 1][i - 1] = (h[i - 2] + h[i - 1]) / 3
  237. R._data[i - 1][i] = h[i - 1] / 6
  238. R._data[i - 1][i - 2] = h[i - 2] / 6
  239. }
  240. }
  241. console.log('LA matrice R :\n', R)
  242. const Rinv = inv(R)
  243. console.log('La matrice inverse de R :\n', Rinv)
  244. const M = multiply(Rinv, F)
  245. console.log('La matrice M = Rinv*F :\n', M)
  246. const C = matrix()
  247. const C2 = matrix()
  248. C.resize([n - 1], 0)
  249. C2.resize([n - 1], 0)
  250. for (let i = 1; i <= n - 1; i++) {
  251. C._data[i - 1] = (y[i] - y[i - 1]) / h[i - 1] - h[i - 1] * (M._data[i] - M._data[i - 1]) / 6
  252. C2._data[i - 1] = y[i - 1] - M._data[i - 1] * h[i - 1] * h[i - 1] / 6
  253. }
  254. console.log('La matrice C :\n', C)
  255. console.log('La matrice C2 :\n', C2)
  256. this.F = F
  257. this.R = R
  258. this.M = M
  259. this.h = h
  260. this.C = C
  261. this.C2 = C2
  262. this.x = x
  263. this.y = y
  264. this.image = function (X) {
  265. let trouveK = false
  266. let k = 0; let f
  267. for (let i = 2; i <= n; i++) {
  268. if (X >= x[i - 2] && X <= x[i - 1]) {
  269. k = i
  270. trouveK = true
  271. break
  272. }
  273. }
  274. if (!trouveK) {
  275. return false
  276. } else {
  277. const i = k
  278. f = a => (F._data[i - 1] * (a - x[i - 2]) ** 3 + F._data[i - 2] * (x[i - 1] - a) ** 3) / (6 * h[i - 1]) + (y[i - 1] / h[i - 1] - F._data[i - 1] * h[i - 1] / 6) * (a - x[i - 2]) + (y[i - 2] / h[i - 1] - F._data[i - 2] * h[i - 1] / 6) * (x[i - 1] - a)
  279. return f(X)
  280. }
  281. }
  282. // une autre façon de calculer l'image... laquelle est la plus rapide ?
  283. /* this.image = function (X) {
  284. let trouveK = false
  285. let k = 0
  286. for (let i = 0; i < n - 1; i++) {
  287. if (X > x[i] && X < x[i + 1]) {
  288. k = i
  289. trouveK = true
  290. break
  291. } else if (egal(X, x[i])) {
  292. return y[i]
  293. } else if (egal(X, x[i + 1])) {
  294. return y[i + 1]
  295. }
  296. }
  297. if (!trouveK) {
  298. return false
  299. } else {
  300. return (M._data[k - 1] * (x[k] - X) ** 3 + M._data[k] * (X - x[k - 1]) ** 3) / (6 * h[k - 1]) + C._data[k - 1] * (X - x[k - 1]) + C2._data[k - 1]
  301. }
  302. }
  303. */
  304. }
  305. }
  306. export function spline (tabNoeuds) {
  307. return new Spline(tabNoeuds)
  308. }
  309. /**
  310. * Fonction qui trie des couples de coordonnées pour les remettre dans l'ordre des x croissant
  311. * @author Jean-Claude Lhote
  312. */
  313. export function trieCouples (x, y) {
  314. let xInter, yInter
  315. for (let i = 0; i < x.length - 1; i++) {
  316. for (let j = i + 1; j < x.length; j++) {
  317. if (x[i] > x[j]) {
  318. xInter = x[i]
  319. x[i] = x[j]
  320. x[j] = xInter
  321. yInter = y[i]
  322. y[i] = y[j]
  323. y[j] = yInter
  324. } else if (egal(x[i], x[j])) {
  325. console.log('Deux couples ont la même valeur de x ! je ne peux pas trier')
  326. return false
  327. }
  328. }
  329. }
  330. return true
  331. }
  332. /**
  333. * inspiré de https://yahiko.developpez.com/tutoriels/introduction-interpolation/?page=page_8#L8-3
  334. * La spline de Catmull-Rom utilise ici un tableau d'ordonnées successives pour des abscisses équiréparties.
  335. * Donc on donne le tableau des valeurs de y, l'abscisse correspondant à la première valeur de y et le pas (step) permettant de passer d'une abscisse à la suivante.
  336. * Adaptation pour Mathalea
  337. * @property {number[]} x liste des abscisses des noeuds (rempli à partir de x0 et step)
  338. * @property {number[]} y liste des ordonnées des noeuds
  339. * @property {number} n nombre de noeuds
  340. * @property {Polynome[]} polys liste des polynomes correspondants à chaque intervalle
  341. * @property {Function[]} fonctions liste des fonctions correspondantes à chaque polynome
  342. * @method {(number)=>number[]} solve(y) retourne les antécédents de y
  343. * @methode {number=>number} image(x) retourne l'image de x par la fonction
  344. * @author Jean-Claude Lhote
  345. */
  346. class SplineCatmullRom {
  347. /**
  348. *
  349. * @param {number[]} tabY liste des valeurs de y au niveau des noeuds (sa longueur détermine le nombre d'intervalles
  350. * @param {number} x0 l'abscisse du début de l'intervalle de définition
  351. * @param {number} step le pas entre chaque valeur de x pour les différents noeuds successifs
  352. */
  353. constructor ({ tabY = [], x0 = -5, step = 1 }) {
  354. this.x = []
  355. this.y = []
  356. this.n = tabY.length // on a n valeurs de y et donc de x, soit n-1 intervalles numérotés de 1 à n-1.
  357. this.step = step // on en a besoin pour la dérivée...
  358. for (let i = 0; i < this.n; i++) {
  359. this.x[i] = x0 + step * i
  360. this.y[i] = tabY[i]
  361. }
  362. this.polys = this.definePolys()
  363. this.fonctions = this.convertPolyFunction()
  364. }
  365. /**
  366. * définis les polynomes de CatMulRom
  367. * @returns {Polynome[]}
  368. */
  369. definePolys () {
  370. const polys = []
  371. for (let i = 1; i < this.n; i++) {
  372. let y0, y1, y2, y3
  373. if (i === 1) { // on est dans l'intervalle [x0,x1] le premier intervalle. i est le numéro de l'intervalle.
  374. y1 = this.y[i - 1]
  375. y2 = this.y[i]
  376. y0 = 2 * y1 - y2
  377. y3 = this.y[i + 1]
  378. } else if (i === this.n - 1) { // on est dans le dernier intervalle [xn-2,xn-1]
  379. y0 = this.y[i - 2]
  380. y1 = this.y[i - 1]
  381. y2 = this.y[i]
  382. y3 = 2 * y2 - y1
  383. } else {
  384. y0 = this.y[i - 2]
  385. y1 = this.y[i - 1]
  386. y2 = this.y[i]
  387. y3 = this.y[i + 1]
  388. }
  389. // t = (x - this.x[i - 1]) / (this.x[i] - this.x[i - 1])
  390. const k = 1 / (this.x[i] - this.x[i - 1])
  391. const t0 = new Polynome({ isUseFraction: false, coeffs: [-this.x[i - 1], 1] })
  392. const t = t0.multiply(k)
  393. const t2 = t.multiply(t)
  394. const t3 = t2.multiply(t)
  395. const b0 = t.multiply(-1).add(t2.multiply(2)).add(t3.multiply(-1)) // -t + 2 * t2 - t3
  396. const b1 = t3.multiply(3).add(t2.multiply(-5)).add(2) // b1 = 2 - 5 * t2 + 3 * t3
  397. const b2 = t.add(t2.multiply(4)).add(t3.multiply(-3)) // b2 = t + 4 * t2 - 3 * t3
  398. const b3 = t3.add(t2.multiply(-1)) // b3 = -t2 + t3 // tous les bi sont de degré 3 en x
  399. // pol est le polynome de degré 3 pour cet intervalle !
  400. const pol = b0.multiply(y0).add(b1.multiply(y1)).add(b2.multiply(y2)).add(b3.multiply(y3)).multiply(0.5)// (b0 * y0 + b1 * y1 + b2 * y2 + b3 * y3) / 2
  401. polys.push(pol)
  402. }
  403. return polys
  404. }
  405. /**
  406. * convertit les polynomes en fonctions
  407. * @returns {Function[]}
  408. */
  409. convertPolyFunction () {
  410. const f = []
  411. debugger
  412. for (let i = 0; i < this.n - 1; i++) {
  413. f.push(this.polys[i].fonction)
  414. }
  415. return f
  416. }
  417. solve (y) {
  418. const antecedents = []
  419. for (let i = 0; i < this.polys.length; i++) {
  420. const polEquation = this.polys[i].add(-y) // Le polynome dont les racines sont les antécédents de y
  421. // Algebrite n'aime pas beaucoup les coefficients decimaux...
  422. try {
  423. const liste = polynomialRoot(...polEquation.monomes)
  424. for (const valeur of liste) {
  425. let arr
  426. if (typeof valeur === 'number') {
  427. arr = round(valeur, 3)
  428. } else { // complexe !
  429. const module = valeur.toPolar().r
  430. if (module < 1e-5) { // module trop petit pour être complexe, c'est 0 !
  431. arr = 0
  432. } else {
  433. if (abs(valeur.arg()) < 0.01 || (abs(valeur.arg() - acos(-1)) < 0.01)) { // si l'argument est proche de 0 ou de Pi
  434. arr = round(valeur.re, 3) // on prend la partie réelle
  435. } else {
  436. arr = null // c'est une vraie racine complexe, du coup, on prend null
  437. }
  438. }
  439. }
  440. if (arr !== null && arr >= this.x[i] && arr <= this.x[i + 1]) {
  441. if (!antecedents.includes(arr)) {
  442. antecedents.push(arr)
  443. }
  444. }
  445. }
  446. } catch (e) {
  447. console.log(e)
  448. }
  449. }
  450. return antecedents
  451. }
  452. get fonction () {
  453. return x => this.image(x)
  454. }
  455. image (x) {
  456. let trouveK = false
  457. let k = 0
  458. for (let i = 0; i < this.n - 1; i++) {
  459. if (x >= this.x[i] && x <= this.x[i + 1]) {
  460. k = i
  461. trouveK = true
  462. break
  463. }
  464. }
  465. if (!trouveK) {
  466. const intervalle = `D = [${this.x[0]} ; ${this.x[this.n - 1]}]`
  467. window.notify('SplineCatmullRom : la valeur de x fournie n\'est pas dans lìntervalle de définition de la fonction', { x, intervalle })
  468. return NaN
  469. } else {
  470. return this.fonctions[k](x)
  471. }
  472. }
  473. /**
  474. * retourne une nouvelle splineCatmulRom correspondant à la fonction dérivée de this.
  475. */
  476. get derivee () {
  477. const derivees = []
  478. for (let i = 0; i < this.n; i++) {
  479. derivees.push(this.polys[Math.min(i, this.n - 2)].derivee().fonction(this.x[i]))
  480. }
  481. const maSpline = new SplineCatmullRom({ tabY: derivees, x0: this.x[0], step: this.step })
  482. for (let i = 0; i < this.n - 1; i++) { // on redéfinit les polynomes
  483. maSpline.polys[i] = this.polys[i].derivee()
  484. }
  485. maSpline.fonctions = maSpline.convertPolyFunction() // on remets les 'bonnes' fonctions
  486. return maSpline
  487. }
  488. }
  489. /**
  490. * inspiré de https://yahiko.developpez.com/tutoriels/introduction-interpolation/?page=page_8#L8-3
  491. * La spline de Catmull-Rom utilise ici un tableau d'ordonnées successives pour des abscisses équiréparties.
  492. * Donc on donne le tableau des valeurs de y, l'abscisse correspondant à la première valeur de y et le pas (step) permettant de passer d'une abscisse à la suivante.
  493. * Adaptation pour Mathalea
  494. * @property {number[]} x liste des abscisses des noeuds (rempli à partir de x0 et step)
  495. * @property {number[]} y liste des ordonnées des noeuds
  496. * @property {number} n nombre de noeuds
  497. * @property {Polynome[]} polys liste des polynomes correspondants à chaque intervalle
  498. * @property {Function[]} fonctions liste des fonctions correspondantes à chaque polynome
  499. * @method {(number)=>number[]} solve(y) retourne les antécédents de y
  500. * @methode {number=>number} image(x) retourne l'image de x par la fonction
  501. * @author Jean-Claude Lhote
  502. *
  503. * @param {number[]} tabY liste des valeurs de y au niveau des noeuds (sa longueur détermine le nombre d'intervalles
  504. * @param {number} x0 l'abscisse du début de l'intervalle de définition
  505. * @param {number} step le pas entre chaque valeur de x pour les différents noeuds successifs
  506. */
  507. export function splineCatmullRom ({ tabY = [], x0 = -5, step = 1 }) {
  508. return new SplineCatmullRom({ tabY, x0, step })
  509. }
  510. /**
  511. * @param {boolean} useFraction si false, les coefficients ne seront pas convertis en fractions. (true par défaut, rendant l'expression mathématique inutilisable avec Algebrite)
  512. * @param {boolean} rand Donner true si on veut un polynôme aléatoire
  513. * @param {number} deg à fournir >=0 en plus de rand === true pour fixer le degré
  514. * @param {Array} coeffs liste de coefficients par ordre de degré croissant OU liste de couples [valeurMax, relatif?]
  515. * @author Jean-Léon Henry, Jean-Claude Lhote
  516. * @example Polynome({ coeffs:[0, 2, 3] }) donne 3x²+2x
  517. * @example Polynome({ rand:true, deg:3 }) donne un ax³+bx²+cx+d à coefficients entiers dans [-10;10]\{0}
  518. * @example Polynome({ rand:true, coeffs:[[10, true], [0], [5, false]] }) donne un ax²+b avec a∈[1;5] et b∈[-10;10]\{0}
  519. * Les monomes sont maintenant stockés sous forme de fractions (même pour les entiers)
  520. */
  521. export class Polynome {
  522. constructor ({ isUseFraction = true, rand = false, deg = -1, coeffs = [[10, true], [10, true]] }) {
  523. this.isUseFraction = isUseFraction
  524. if (rand) {
  525. if (largerEq(deg, 0)) {
  526. // on construit coeffs indépendamment de la valeur fournie
  527. coeffs = new Array(deg + 1)
  528. coeffs.fill([10, true])
  529. }
  530. // Création de this.monomes
  531. this.monomes = coeffs.map(function (el, i) {
  532. if (isUseFraction) {
  533. if (equal(el[0], 0)) {
  534. return fraction(0)
  535. } else {
  536. return el[1] ? fraction(choice([-1, 1]) * randint(1, number(el[0]))) : fraction(randint(1, number(el[0])))
  537. }
  538. } else {
  539. if (equal(el[0], 0)) {
  540. return 0
  541. } else {
  542. return el[1] ? choice([-1, 1]) * randint(1, number(el[0])) : randint(1, number(el[0]))
  543. }
  544. }
  545. })
  546. } else {
  547. // les coeffs sont fourni
  548. this.monomes = coeffs.map(function (el, i) {
  549. if (isUseFraction) {
  550. return fraction(el)
  551. } else {
  552. return el
  553. }
  554. })
  555. }
  556. this.deg = this.monomes.length - 1
  557. }
  558. isMon () { return this.monomes.filter(el => unequal(el, 0)).length === 1 }
  559. /**
  560. * @param {boolean} alg si true alors le coefficient dominant est doté de son signe +/-
  561. * @returns {string} expression mathématique compatible avec Algebrite
  562. */
  563. toMathExpr (alg = false) {
  564. let res = ''
  565. let maj = ''
  566. for (const [i, c] of this.monomes.entries()) {
  567. switch (i) {
  568. case this.deg: {
  569. const coeffD = alg ? ecritureAlgebriqueSauf1(c) : this.deg === 0 ? ecritureAlgebrique(c) : rienSi1(c)
  570. switch (this.deg) {
  571. case 1:
  572. maj = equal(c, 0) ? '' : `${coeffD}x`
  573. break
  574. case 0:
  575. maj = equal(c, 0) ? '' : `${coeffD}`
  576. break
  577. default:
  578. maj = equal(c, 0) ? '' : `${coeffD}x^${i}`
  579. }
  580. break
  581. }
  582. case 0:
  583. maj = equal(c, 0) ? '' : ecritureAlgebrique(c)
  584. break
  585. case 1:
  586. maj = equal(c, 0) ? '' : `${ecritureAlgebriqueSauf1(c)}x`
  587. break
  588. default:
  589. maj = equal(c, 0) ? '' : `${ecritureAlgebriqueSauf1(c)}x^${i}`
  590. break
  591. }
  592. maj = maj.replace(/\s/g, '').replace(',', '.')
  593. res = maj + res
  594. }
  595. return res
  596. }
  597. /**
  598. * @param {boolean} alg si true alors le coefficient dominant est doté de son signe +/-
  599. * @returns {string} expression mathématique
  600. */
  601. toLatex (alg = false) {
  602. let res = ''
  603. let maj = ''
  604. for (const [i, c] of this.monomes.entries()) {
  605. switch (i) {
  606. case this.deg: {
  607. const coeffD = alg ? ecritureAlgebriqueSauf1(this.isUseFraction ? fraction(c) : c) : this.deg === 0 ? (this.isUseFraction ? fraction(c).toLatex() : c) : rienSi1(this.isUseFraction ? fraction(c) : c)
  608. switch (this.deg) {
  609. case 1:
  610. maj = equal(c, 0) ? '' : `${coeffD}x`
  611. break
  612. case 0:
  613. maj = equal(c, 0) ? '' : `${coeffD}`
  614. break
  615. default:
  616. maj = equal(c, 0) ? '' : `${coeffD}x^${i}`
  617. }
  618. break
  619. }
  620. case 0:
  621. maj = equal(c, 0) ? '' : ecritureAlgebrique(this.isUseFraction ? fraction(c) : c)
  622. break
  623. case 1:
  624. maj = equal(c, 0) ? '' : `${ecritureAlgebriqueSauf1(this.isUseFraction ? fraction(c) : c)}x`
  625. break
  626. default:
  627. maj = equal(c, 0) ? '' : `${ecritureAlgebriqueSauf1(this.isUseFraction ? fraction(c) : c)}x^${i}`
  628. break
  629. }
  630. res = maj + res
  631. }
  632. return res
  633. }
  634. /**
  635. * Polynome type conversion to String
  636. * @returns le résultat de toMathExpr()
  637. */
  638. toString () {
  639. return this.toLatex()
  640. }
  641. /**
  642. * Ajoute un Polynome ou une constante
  643. * @param {Polynome|number|Fraction} p
  644. * @example p.add(3) pour ajouter la constante 3 à p
  645. * @returns {Polynome} this+p
  646. */
  647. add (p) {
  648. const isUseFraction = this.isUseFraction
  649. if (typeof p === 'number' || p.type === 'Fraction') {
  650. const coeffs = this.monomes
  651. coeffs[0] = add(this.monomes[0], p)
  652. return new Polynome({ isUseFraction, coeffs })
  653. } else if (p instanceof Polynome) {
  654. const degSomme = max(this.deg, p.deg)
  655. const pInf = equal(p.deg, degSomme) ? this : p
  656. const pSup = equal(p.deg, degSomme) ? p : this
  657. const coeffSomme = isUseFraction
  658. ? pSup.monomes.map(function (el, index) { return index <= pInf.deg ? fraction(add(el, pInf.monomes[index])) : fraction(el) })
  659. : pSup.monomes.map(function (el, index) { return index <= pInf.deg ? add(el, pInf.monomes[index]) : el })
  660. return new Polynome({ isUseFraction, coeffs: coeffSomme })
  661. } else {
  662. window.notify('Polynome.add(arg) : l\'argument n\'est ni un nombre, ni un polynome', { p })
  663. }
  664. }
  665. /**
  666. *
  667. * @param {Polynome|number|Fraction} q Polynome, nombre ou fraction
  668. * @example poly = poly.multiply(fraction(1,3)) divise tous les coefficients de poly par 3.
  669. * @returns q fois this
  670. */
  671. multiply (q) {
  672. const isUseFraction = this.isUseFraction
  673. let coeffs
  674. if (typeof q === 'number' || q.type === 'Fraction') {
  675. coeffs = isUseFraction
  676. ? this.monomes.map(function (el, i) { return fraction(multiply(el, q)) })
  677. : this.monomes.map(function (el, i) { return multiply(el, q) })
  678. } else if (q instanceof Polynome) {
  679. coeffs = new Array(this.deg + q.deg + 1)
  680. coeffs.fill(0)
  681. for (let i = 0; i <= this.deg; i++) {
  682. for (let j = 0; j <= q.deg; j++) {
  683. coeffs[i + j] = add(coeffs[i + j], multiply(this.monomes[i], q.monomes[j]))
  684. }
  685. }
  686. } else {
  687. window.notify('Polynome.multiply(arg) : l\'argument n\'est ni un nombre, ni un polynome', { q })
  688. }
  689. return new Polynome({ isUseFraction, coeffs })
  690. }
  691. /**
  692. * Retourne la dérivée
  693. * @returns {Polynome} dérivée de this
  694. */
  695. derivee () {
  696. const coeffDerivee = this.isUseFraction
  697. ? this.monomes.map(function (el, i) { return fraction(multiply(i, el)) })
  698. : this.monomes.map(function (el, i) { return multiply(i, el) })
  699. coeffDerivee.shift()
  700. return new Polynome({ isUseFraction: this.isUseFraction, coeffs: coeffDerivee })
  701. }
  702. /**
  703. * Appelle toMathExpr
  704. * @param {Array} coeffs coefficients du polynôme par ordre de degré croissant
  705. * @param {boolean} alg si true alors le coefficient dominant est doté de son signe +/-
  706. * @returns {string} expression du polynome
  707. */
  708. static print (coeffs, alg = false) {
  709. const p = new Polynome({ coeffs })
  710. return p.toLatex(alg)
  711. }
  712. /**
  713. * la fonction à utiliser pour tracer la courbe par exemple ou calculer des valeurs comme dans pol.image()
  714. * const f = pol.fonction est une fonction utilisable dans courbe()
  715. * @returns {function(number): number}
  716. */
  717. get fonction () {
  718. return x => this.monomes.reduce((val, current) => val + current * x ** this.monomes.findIndex(coeff => coeff === current))
  719. }
  720. /**
  721. * Pour calculer l'image d'un nombre
  722. * @param x
  723. * @returns {math.Fraction | number | int} // à mon avis ça ne retourne que des number...
  724. */
  725. image (x) {
  726. return this.fonction(x)
  727. }
  728. }
  729. function angleOppose (angle) { // retourne l'angle opposé d'un angle du premier cadrant (sinon, on pourrait avoir plusieurs signe '-' collés ensemble)
  730. if (angle.degres === '0') {
  731. return angle
  732. } else {
  733. return new Angle({ degres: '-' + angle.degres, cos: angle.cos, sin: angle.sin === '0' ? angle.sin : opposeStringArray(angle.sin), tan: angle.tan === '0' ? angle.tan : '-' + angle.tan, radians: '-' + angle.radians })
  734. }
  735. }
  736. function complementaireRad (angleEnRadian) { // retourne la mesure en radians du complémentaire d'un angle du premier quadrant donné également en radians
  737. switch (angleEnRadian) {
  738. case '\\dfrac{\\pi}{4}':
  739. return angleEnRadian
  740. case '\\dfrac{\\pi}{6}':
  741. return '\\dfrac{\\pi}{3}'
  742. case '\\dfrac{\\pi}{3}':
  743. return '\\dfrac{\\pi}{6}'
  744. case '\\dfrac{\\pi}{2}' :
  745. return '0'
  746. case '0' :
  747. return '\\dfrac{\\pi}{2}'
  748. }
  749. }
  750. function supplementaireRad (angleEnRadian) { // retourne la mesure en radians du supplémentaire d'un angle du premier quadrant donné également en radians
  751. switch (angleEnRadian) {
  752. case '\\dfrac{\\pi}{4}':
  753. return '\\dfrac{3\\pi}{4}'
  754. case '\\dfrac{\\pi}{6}':
  755. return '\\dfrac{5\\pi}{6}'
  756. case '\\dfrac{\\pi}{3}':
  757. return '\\dfrac{2\\pi}{3}'
  758. case '\\dfrac{\\pi}{2}' :
  759. return '\\dfrac{\\pi}{2}'
  760. case '0' :
  761. return '\\pi'
  762. }
  763. }
  764. function inverseTan (angle) {
  765. switch (angle.tan) {
  766. case '\\infin':
  767. case '-\\infin':
  768. return '0'
  769. case '1': return '1'
  770. case '\\sqrt{3}': return '\\dfrac{\\sqrt{3}}{3}'
  771. case '\\dfrac{\\sqrt{3}}{3}': return '\\sqrt{3}'
  772. }
  773. }
  774. function angleComplementaire (angle) { // retourne l'angle complémentaire d'un angle du premier cadrant
  775. return new Angle({ degres: (90 - parseInt(angle.degres)).toString(), cos: angle.sin, sin: angle.cos, tan: inverseTan(angle), radians: complementaireRad(angle.radians) })
  776. }
  777. function angleSupplementaire (angle) { // retourne l'angle supplémentaire d'un angle du premier cadrant
  778. return new Angle({ degres: (180 - parseInt(angle.degres)).toString(), cos: angle.cos === '0' ? '0' : opposeStringArray(angle.cos), sin: angle.sin, tan: angle.tan === '\\infin' ? '\\infin' : '-' + angle.tan, radians: supplementaireRad(angle.radians) })
  779. }
  780. function opposeStringArray (value) {
  781. if (Array.isArray(value)) {
  782. const result = []
  783. for (const e of value) {
  784. result.push('-' + e)
  785. }
  786. return result
  787. } else return '-' + value
  788. }
  789. /**
  790. * @class
  791. * Crée un objet qui contient les propriétés suivantes : degres, cos, sin, tan et radians.
  792. */
  793. export class Angle {
  794. /**
  795. * @constructor
  796. * @param {object} param
  797. * @param {string} [param.degres] mesure de l'angle en degrés sous forme de string
  798. * @param {string} [param.cos] valeur du cosinus sous forme de string
  799. * @param {string} [param.sin] valeur du sinus sous forme de string
  800. * @param {string} [param.tan] valeur de la tangente sous forme de string
  801. * @param {string} [param.radians] mesure de l'angle en radians sous forme de string
  802. * @example const a = new Angle({ degres: '90', radians: '\\dfrac{5\\pi}{2}' }) => {degres: '90', cos: '0', sin: '1', tan: '\\infin', radians: '\\dfrac{5\\pi}{2}'}
  803. * @author Jean-Claude Lhote
  804. */
  805. constructor ({ degres, cos, sin, tan, radians }) { // il faut au moins fournir la mesure en degrés
  806. this.degres = degres
  807. const anglesDeBase = [
  808. { degres: '90', cos: '0', sin: '1', tan: '\\infin', radians: '\\dfrac{\\pi}{2}' },
  809. { degres: '45', cos: '\\dfrac{\\sqrt{2}}{2}', sin: '\\dfrac{\\sqrt{2}}{2}', tan: '1', radians: '\\dfrac{\\pi}{4}' },
  810. { degres: '60', cos: ['\\dfrac{1}{2}', '0.5'], sin: '\\dfrac{\\sqrt{3}}{2}', tan: '\\sqrt{3}', radians: '\\dfrac{\\pi}{3}' },
  811. { degres: '30', sin: ['\\dfrac{1}{2}', '0.5'], cos: '\\dfrac{\\sqrt{3}}{2}', tan: '\\dfrac{\\sqrt{3}}{3}', radians: '\\dfrac{\\pi}{6}' },
  812. { degres: '0', cos: '1', sin: '0', tan: '0', radians: '0' }
  813. ]
  814. const angle = anglesDeBase.find(el => el.degres === (parseInt(degres) % 360).toString())
  815. if (angle === undefined) { // si ce n'est pas un des anglesDeBase, alors il faut les autres arguments.
  816. this.cos = cos
  817. this.sin = sin
  818. this.tan = tan
  819. this.radians = radians
  820. } else { // si l'angle en degré est fourni, on aura par défaut les valeurs de l'angle de base si les paramètres ne sont pas donnés
  821. this.cos = cos || angle.cos
  822. this.sin = sin || angle.sin
  823. this.tan = tan || angle.tan
  824. this.radians = radians || angle.radians
  825. }
  826. }
  827. }
  828. export const anglesDeBase = [
  829. new Angle({ degres: '90', cos: '0', sin: '1', tan: '\\infin', radians: '\\dfrac{\\pi}{2}' }),
  830. new Angle({ degres: '45', cos: '\\dfrac{\\sqrt{2}}{2}', sin: '\\dfrac{\\sqrt{2}}{2}', tan: '1', radians: '\\dfrac{\\pi}{4}' }),
  831. new Angle({ degres: '60', cos: ['\\dfrac{1}{2}', '0.5'], sin: '\\dfrac{\\sqrt{3}}{2}', tan: '\\sqrt{3}', radians: '\\dfrac{\\pi}{3}' }),
  832. new Angle({ degres: '30', sin: ['\\dfrac{1}{2}', '0.5'], cos: '\\dfrac{\\sqrt{3}}{2}', tan: '\\dfrac{\\sqrt{3}}{3}', radians: '\\dfrac{\\pi}{6}' }),
  833. new Angle({ degres: '0', cos: '1', sin: '0', tan: '0', radians: '0' })
  834. ]
  835. function moduloDeg (angleEnDegre, k) {
  836. const coef = 360 / parseInt(angleEnDegre)
  837. if (angleEnDegre === '0') {
  838. return ((2 * k) * 180).toString()
  839. } else return ((coef * k + 1) * parseInt(angleEnDegre)).toString()
  840. }
  841. function moduloRad (angleEnDegre, k) {
  842. const coef = 360 / parseInt(angleEnDegre)
  843. if (angleEnDegre === '0') {
  844. return `${2 * k}\\pi`
  845. } else return `\\dfrac{${coef * k + 1}\\pi}{${coef / 2}}`
  846. }
  847. /**
  848. *
  849. * @param {Angle} angle
  850. * @param {number} k On part de l'objet angle et on ajoute 2 * k * pi
  851. * @returns {Angle}
  852. */
  853. function angleModulo (angle, k) {
  854. return new Angle({ degres: moduloDeg(angle.degres, k), cos: angle.cos, sin: angle.sin, tan: angle.tan, radians: moduloRad(angle.degres, k) })
  855. }
  856. /**
  857. * @param {object} param
  858. * @param {boolean} [param.associes] false pour niveau1 (quart de cercle) uniquement, true pour ajouter niveau2 (cercle trigo)
  859. * @param {number[]} [param.modulos] liste des k à utiliser pour ajouter les angles modulo 2k*Pi
  860. * @returns {{liste1: string[], liste2: string[], liste3: string[]}} liste1, liste2, liste3 les listes (niveau2 contient niveau1 et niveau3 contient niveau2)
  861. * @author Jean-Claude Lhote
  862. */
  863. export function valeursTrigo ({ associes = true, modulos = [-1, 1] }) {
  864. let mesAngles = anglesDeBase.slice()
  865. const mesAnglesNiv1 = mesAngles.slice()
  866. const nombreAnglesDeBase = mesAngles.length
  867. // ici on complète la liste avec tous les angles associés en faisant attention de ne pas ajouter deux fois les mêmes.
  868. for (let i = 0; i < nombreAnglesDeBase; i++) {
  869. mesAngles.push(angleOppose(mesAngles[i]), angleComplementaire(mesAngles[i]), angleSupplementaire(mesAngles[i]))
  870. }
  871. // On supprime les doublons en comparant la mesure en degrés
  872. mesAngles = [...new Map(mesAngles.map(item => [item.degres, item])).values()]
  873. const mesAnglesNiv2 = mesAngles.slice()
  874. for (let i = 0; i < nombreAnglesDeBase; i++) {
  875. for (const k of modulos) {
  876. if (k !== 0) mesAngles.push(angleModulo(mesAngles[i % nombreAnglesDeBase], k))
  877. }
  878. }
  879. const mesAnglesNiv3 = mesAngles.slice()
  880. return { liste1: mesAnglesNiv1, liste2: mesAnglesNiv2, liste3: mesAnglesNiv3 }
  881. }