import { number, add, unequal, largerEq, fraction, equal, multiply, inv, matrix, max, polynomialRoot, round, acos, abs } from 'mathjs'
import FractionX from './FractionEtendue.js'
import { calcul, arrondi, ecritureAlgebrique, egal, randint, rienSi1, ecritureAlgebriqueSauf1, choice } from './outils.js'
/**
* Convertit un angle de radian vers degrés et fonction inverse
* @Example
* // PI->180
* @author Jean-Claude Lhote
*/
export function degres (radians) {
return radians * 180 / Math.PI
}
export function radians (degres) {
return degres * Math.PI / 180
}
/**
* @param {number} a angle en degrés
* @returns flottant : le cosinus de l'angle
* @author Jean-Claude Lhote
*/
export function degCos (a) {
return calcul(Math.cos(radians(a)))
}
/**
* @param {number} a angle en degrés
* @returns flottant : le sinus de l'angle
* @author Jean-Claude Lhote
*/
export function degSin (a) {
return calcul(Math.sin(radians(a)))
}
/**
* @param {number} a angle en degrés
* @returns flottant : la tangente de l'angle
* @author Jean-Claude Lhote
*/
export function degTan (a) {
return calcul(Math.tan(radians(a)))
}
/**
* @param {number} un nombre qui correspond au cosinus de l'angle
* @returns flottant : la mesure de l'angle en degrés
* @author Jean-Claude Lhote
*/
export function degAcos (x) {
return arrondi(degres(Math.acos(x)), 1)
}
/**
* @param {number} un nombre qui correspond au sinus de l'angle
* @returns flottant : la mesure de l'angle en degrés
* @author Jean-Claude Lhote
*/
export function degAsin (x) {
return arrondi(degres(Math.asin(x)), 1)
}
/**
* @param {number} un nombre qui correspond à la tangente de l'angle
* @returns flottant : la mesure de l'angle en degrés
* @author Jean-Claude Lhote
*/
export function degAtan (x) {
return arrondi(degres(Math.atan(x)), 1)
}
/**
* retourne un décimal sans décimales bizarres
* Déprécié -> utiliser la classe Decimal de Decimal.js pour travailler avec de vrais décimaux
* @author Jean-Claude Lhote
*/
export function calcule (a, arrondir = false) {
if (!arrondir) {
if (typeof a === 'string') {
return parseFloat(Number(a).toFixed(6))
} else {
return parseFloat(a.toFixed(6))
}
} else {
if (typeof a === 'string') {
return parseFloat(Number(a).toFixed(arrondir))
} else {
return parseFloat(a.toFixed(arrondir))
}
}
}
/**
* retourne une chaine contenant le résultat du calcul avec la virgule comme séparateur et pas de décimales bizarres
* déprécié -> utiliser Decimal.toString() pour travailler avec les décimaux
* @author Jean-Claude Lhote
*/
export function calculeS (a, arrondir = false) {
let result
if (!arrondir) {
if (typeof a === 'string') {
result = Number(a).toFixed(6).split('.')
} else {
result = a.toFixed(6).split('.')
}
} else {
if (typeof a === 'string') {
result = Number(a).toFixed(arrondir).split('.')
} else {
result = a.toFixed(arrondir).split('.')
}
}
result[1] = result[1].replace(/[0]*$/, '')
if (result[1].length !== 0) { return result[0] + ',' + result[1] } else return result[0]
}
export function fractionLatexToMathjs (fractionLatex) {
const parts = fractionLatex.split('{')
const num = Number(parts[1].substring(0, parts[1].length - 1))
const den = Number(parts[2].substring(0, parts[2].length - 1))
return new FractionX(num, den)
}
/**
* delta(true) retourne dans un tableau des valeurs de a, b, c telles que b*b-4*a*c >0
* delta(false) retourne dans un tableau des valeurs de a, b, c telles que b*b-4*a*c <0
* @author Jean-Claude Lhote
*/
export function choisiDelta (positif) {
let d, a, b, c
do {
a = randint(-5, 5, 0)
b = randint(-5, 5, 0)
c = randint(-5, 5, 0)
d = b * b - 4 * a * c
} while (positif ? d <= 0 : d >= 0)
return [a, b, c]
}
/**
* onction qui retourne un polynome du second degré correctement écrit.
* @param {number} a
* @param {number} b
* @param {number} c
* @returns {string}
*/
export function expTrinome (a, b, c) {
let expr = ''
if (typeof a === 'number') {
switch (a) {
case 0:
break
case -1:
expr += '-x^2'
break
case 1:
expr += 'x^2'
break
default:
expr += `${a}x^2`
break
}
} else {
expr += `${a}x^2`
}
if (typeof b === 'number') {
switch (b) {
case 0:
break
case -1:
expr += '-x'
break
case 1:
expr += '+x'
break
default:
if (a === 0) {
expr += `${b}`
} else expr += `${ecritureAlgebrique(b)}x`
break
}
} else {
if (a === 0) {
expr += `${b}x`
} else {
expr += `+${b}x`
}
}
if (typeof c === 'number') {
if (a === 0 && b === 0) {
expr += `${c}`
} else {
if (c !== 0) {
expr += `${ecritureAlgebrique(c)}`
}
}
} else {
expr += `+${c}`
}
return expr
}
/**
* inspiré de l'article 'Algorithme de calcul d'une courbe spline de lissage'
* de cet article 'spline' de wikipedia : https://fr.wikipedia.org/wiki/Spline
* ça fonctionne quand les noeuds sont 'bien répartis'... ce qui n'est pas évident...
* Les noeuds sont des couples (x,y)
* On lui préférera la Spline de Catmull-Rom ci-dessous.
* Adaptation pour Mathalea
* @author Jean-Claude Lhote
*/
class Spline {
constructor (tabNoeuds) {
const x = []
const y = []
const h = []
const F = matrix()
const R = matrix()
let n
for (let i = 0; i < tabNoeuds.length; i++) {
x[i] = tabNoeuds[i][0]
y[i] = tabNoeuds[i][1]
}
if (trieCouples(x, y)) { // On mets les couples (x,y) dans l'ordre des x croissants.
n = x.length
} else {
console.log('il y a un problème avec ce tableau des noeuds')
return false
}
for (let i = 0; i < n; i++) {
console.log('(', x[i], ';', y[i], ')')
}
for (let i = 0; i < n - 1; i++) {
h[i] = x[i + 1] - x[i] // on calcule les amplitudes des intervalles.
}
console.log(' Les intervalles :\n ', h)
F.resize([n], 0)
for (let i = 2; i < n; i++) { // On construit la matrice des dérivées secondes
F._data[i - 1] = (y[i] - y[i - 1]) / h[i - 1] - (y[i - 1] - y[i - 2]) / h[i - 2]
}
console.log('La matrice des dérivéées secondes :\n', F)
R.resize([n, n], 0)
for (let i = 1; i <= n; i++) { // On construit la matrice carrée de calcul
if (i === 1) {
R._data[0][0] = 1 // seul le premier élément de la première ligne n'est pas nul
} else if (i === n) {
R._data[n - 1][n - 1] = 1 // seul le dernier élément de la dernière ligne n'est pas nul
} else { // on Construit les diagonales n = 2 .. n-1
R._data[i - 1][i - 1] = (h[i - 2] + h[i - 1]) / 3
R._data[i - 1][i] = h[i - 1] / 6
R._data[i - 1][i - 2] = h[i - 2] / 6
}
}
console.log('LA matrice R :\n', R)
const Rinv = inv(R)
console.log('La matrice inverse de R :\n', Rinv)
const M = multiply(Rinv, F)
console.log('La matrice M = Rinv*F :\n', M)
const C = matrix()
const C2 = matrix()
C.resize([n - 1], 0)
C2.resize([n - 1], 0)
for (let i = 1; i <= n - 1; i++) {
C._data[i - 1] = (y[i] - y[i - 1]) / h[i - 1] - h[i - 1] * (M._data[i] - M._data[i - 1]) / 6
C2._data[i - 1] = y[i - 1] - M._data[i - 1] * h[i - 1] * h[i - 1] / 6
}
console.log('La matrice C :\n', C)
console.log('La matrice C2 :\n', C2)
this.F = F
this.R = R
this.M = M
this.h = h
this.C = C
this.C2 = C2
this.x = x
this.y = y
this.image = function (X) {
let trouveK = false
let k = 0; let f
for (let i = 2; i <= n; i++) {
if (X >= x[i - 2] && X <= x[i - 1]) {
k = i
trouveK = true
break
}
}
if (!trouveK) {
return false
} else {
const i = k
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)
return f(X)
}
}
// une autre façon de calculer l'image... laquelle est la plus rapide ?
/* this.image = function (X) {
let trouveK = false
let k = 0
for (let i = 0; i < n - 1; i++) {
if (X > x[i] && X < x[i + 1]) {
k = i
trouveK = true
break
} else if (egal(X, x[i])) {
return y[i]
} else if (egal(X, x[i + 1])) {
return y[i + 1]
}
}
if (!trouveK) {
return false
} else {
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]
}
}
*/
}
}
export function spline (tabNoeuds) {
return new Spline(tabNoeuds)
}
/**
* Fonction qui trie des couples de coordonnées pour les remettre dans l'ordre des x croissant
* @author Jean-Claude Lhote
*/
export function trieCouples (x, y) {
let xInter, yInter
for (let i = 0; i < x.length - 1; i++) {
for (let j = i + 1; j < x.length; j++) {
if (x[i] > x[j]) {
xInter = x[i]
x[i] = x[j]
x[j] = xInter
yInter = y[i]
y[i] = y[j]
y[j] = yInter
} else if (egal(x[i], x[j])) {
console.log('Deux couples ont la même valeur de x ! je ne peux pas trier')
return false
}
}
}
return true
}
/**
* inspiré de https://yahiko.developpez.com/tutoriels/introduction-interpolation/?page=page_8#L8-3
* La spline de Catmull-Rom utilise ici un tableau d'ordonnées successives pour des abscisses équiréparties.
* 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.
* Adaptation pour Mathalea
* @property {number[]} x liste des abscisses des noeuds (rempli à partir de x0 et step)
* @property {number[]} y liste des ordonnées des noeuds
* @property {number} n nombre de noeuds
* @property {Polynome[]} polys liste des polynomes correspondants à chaque intervalle
* @property {Function[]} fonctions liste des fonctions correspondantes à chaque polynome
* @method {(number)=>number[]} solve(y) retourne les antécédents de y
* @methode {number=>number} image(x) retourne l'image de x par la fonction
* @author Jean-Claude Lhote
*/
class SplineCatmullRom {
/**
*
* @param {number[]} tabY liste des valeurs de y au niveau des noeuds (sa longueur détermine le nombre d'intervalles
* @param {number} x0 l'abscisse du début de l'intervalle de définition
* @param {number} step le pas entre chaque valeur de x pour les différents noeuds successifs
*/
constructor ({ tabY = [], x0 = -5, step = 1 }) {
this.x = []
this.y = []
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.
this.step = step // on en a besoin pour la dérivée...
for (let i = 0; i < this.n; i++) {
this.x[i] = x0 + step * i
this.y[i] = tabY[i]
}
this.polys = this.definePolys()
this.fonctions = this.convertPolyFunction()
}
/**
* définis les polynomes de CatMulRom
* @returns {Polynome[]}
*/
definePolys () {
const polys = []
for (let i = 1; i < this.n; i++) {
let y0, y1, y2, y3
if (i === 1) { // on est dans l'intervalle [x0,x1] le premier intervalle. i est le numéro de l'intervalle.
y1 = this.y[i - 1]
y2 = this.y[i]
y0 = 2 * y1 - y2
y3 = this.y[i + 1]
} else if (i === this.n - 1) { // on est dans le dernier intervalle [xn-2,xn-1]
y0 = this.y[i - 2]
y1 = this.y[i - 1]
y2 = this.y[i]
y3 = 2 * y2 - y1
} else {
y0 = this.y[i - 2]
y1 = this.y[i - 1]
y2 = this.y[i]
y3 = this.y[i + 1]
}
// t = (x - this.x[i - 1]) / (this.x[i] - this.x[i - 1])
const k = 1 / (this.x[i] - this.x[i - 1])
const t0 = new Polynome({ isUseFraction: false, coeffs: [-this.x[i - 1], 1] })
const t = t0.multiply(k)
const t2 = t.multiply(t)
const t3 = t2.multiply(t)
const b0 = t.multiply(-1).add(t2.multiply(2)).add(t3.multiply(-1)) // -t + 2 * t2 - t3
const b1 = t3.multiply(3).add(t2.multiply(-5)).add(2) // b1 = 2 - 5 * t2 + 3 * t3
const b2 = t.add(t2.multiply(4)).add(t3.multiply(-3)) // b2 = t + 4 * t2 - 3 * t3
const b3 = t3.add(t2.multiply(-1)) // b3 = -t2 + t3 // tous les bi sont de degré 3 en x
// pol est le polynome de degré 3 pour cet intervalle !
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
polys.push(pol)
}
return polys
}
/**
* convertit les polynomes en fonctions
* @returns {Function[]}
*/
convertPolyFunction () {
const f = []
debugger
for (let i = 0; i < this.n - 1; i++) {
f.push(this.polys[i].fonction)
}
return f
}
solve (y) {
const antecedents = []
for (let i = 0; i < this.polys.length; i++) {
const polEquation = this.polys[i].add(-y) // Le polynome dont les racines sont les antécédents de y
// Algebrite n'aime pas beaucoup les coefficients decimaux...
try {
const liste = polynomialRoot(...polEquation.monomes)
for (const valeur of liste) {
let arr
if (typeof valeur === 'number') {
arr = round(valeur, 3)
} else { // complexe !
const module = valeur.toPolar().r
if (module < 1e-5) { // module trop petit pour être complexe, c'est 0 !
arr = 0
} else {
if (abs(valeur.arg()) < 0.01 || (abs(valeur.arg() - acos(-1)) < 0.01)) { // si l'argument est proche de 0 ou de Pi
arr = round(valeur.re, 3) // on prend la partie réelle
} else {
arr = null // c'est une vraie racine complexe, du coup, on prend null
}
}
}
if (arr !== null && arr >= this.x[i] && arr <= this.x[i + 1]) {
if (!antecedents.includes(arr)) {
antecedents.push(arr)
}
}
}
} catch (e) {
console.log(e)
}
}
return antecedents
}
get fonction () {
return x => this.image(x)
}
image (x) {
let trouveK = false
let k = 0
for (let i = 0; i < this.n - 1; i++) {
if (x >= this.x[i] && x <= this.x[i + 1]) {
k = i
trouveK = true
break
}
}
if (!trouveK) {
const intervalle = `D = [${this.x[0]} ; ${this.x[this.n - 1]}]`
window.notify('SplineCatmullRom : la valeur de x fournie n\'est pas dans lìntervalle de définition de la fonction', { x, intervalle })
return NaN
} else {
return this.fonctions[k](x)
}
}
/**
* retourne une nouvelle splineCatmulRom correspondant à la fonction dérivée de this.
*/
get derivee () {
const derivees = []
for (let i = 0; i < this.n; i++) {
derivees.push(this.polys[Math.min(i, this.n - 2)].derivee().fonction(this.x[i]))
}
const maSpline = new SplineCatmullRom({ tabY: derivees, x0: this.x[0], step: this.step })
for (let i = 0; i < this.n - 1; i++) { // on redéfinit les polynomes
maSpline.polys[i] = this.polys[i].derivee()
}
maSpline.fonctions = maSpline.convertPolyFunction() // on remets les 'bonnes' fonctions
return maSpline
}
}
/**
* inspiré de https://yahiko.developpez.com/tutoriels/introduction-interpolation/?page=page_8#L8-3
* La spline de Catmull-Rom utilise ici un tableau d'ordonnées successives pour des abscisses équiréparties.
* 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.
* Adaptation pour Mathalea
* @property {number[]} x liste des abscisses des noeuds (rempli à partir de x0 et step)
* @property {number[]} y liste des ordonnées des noeuds
* @property {number} n nombre de noeuds
* @property {Polynome[]} polys liste des polynomes correspondants à chaque intervalle
* @property {Function[]} fonctions liste des fonctions correspondantes à chaque polynome
* @method {(number)=>number[]} solve(y) retourne les antécédents de y
* @methode {number=>number} image(x) retourne l'image de x par la fonction
* @author Jean-Claude Lhote
*
* @param {number[]} tabY liste des valeurs de y au niveau des noeuds (sa longueur détermine le nombre d'intervalles
* @param {number} x0 l'abscisse du début de l'intervalle de définition
* @param {number} step le pas entre chaque valeur de x pour les différents noeuds successifs
*/
export function splineCatmullRom ({ tabY = [], x0 = -5, step = 1 }) {
return new SplineCatmullRom({ tabY, x0, step })
}
/**
* @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)
* @param {boolean} rand Donner true si on veut un polynôme aléatoire
* @param {number} deg à fournir >=0 en plus de rand === true pour fixer le degré
* @param {Array} coeffs liste de coefficients par ordre de degré croissant OU liste de couples [valeurMax, relatif?]
* @author Jean-Léon Henry, Jean-Claude Lhote
* @example Polynome({ coeffs:[0, 2, 3] }) donne 3x²+2x
* @example Polynome({ rand:true, deg:3 }) donne un ax³+bx²+cx+d à coefficients entiers dans [-10;10]\{0}
* @example Polynome({ rand:true, coeffs:[[10, true], [0], [5, false]] }) donne un ax²+b avec a∈[1;5] et b∈[-10;10]\{0}
* Les monomes sont maintenant stockés sous forme de fractions (même pour les entiers)
*/
export class Polynome {
constructor ({ isUseFraction = true, rand = false, deg = -1, coeffs = [[10, true], [10, true]] }) {
this.isUseFraction = isUseFraction
if (rand) {
if (largerEq(deg, 0)) {
// on construit coeffs indépendamment de la valeur fournie
coeffs = new Array(deg + 1)
coeffs.fill([10, true])
}
// Création de this.monomes
this.monomes = coeffs.map(function (el, i) {
if (isUseFraction) {
if (equal(el[0], 0)) {
return fraction(0)
} else {
return el[1] ? fraction(choice([-1, 1]) * randint(1, number(el[0]))) : fraction(randint(1, number(el[0])))
}
} else {
if (equal(el[0], 0)) {
return 0
} else {
return el[1] ? choice([-1, 1]) * randint(1, number(el[0])) : randint(1, number(el[0]))
}
}
})
} else {
// les coeffs sont fourni
this.monomes = coeffs.map(function (el, i) {
if (isUseFraction) {
return fraction(el)
} else {
return el
}
})
}
this.deg = this.monomes.length - 1
}
isMon () { return this.monomes.filter(el => unequal(el, 0)).length === 1 }
/**
* @param {boolean} alg si true alors le coefficient dominant est doté de son signe +/-
* @returns {string} expression mathématique compatible avec Algebrite
*/
toMathExpr (alg = false) {
let res = ''
let maj = ''
for (const [i, c] of this.monomes.entries()) {
switch (i) {
case this.deg: {
const coeffD = alg ? ecritureAlgebriqueSauf1(c) : this.deg === 0 ? ecritureAlgebrique(c) : rienSi1(c)
switch (this.deg) {
case 1:
maj = equal(c, 0) ? '' : `${coeffD}x`
break
case 0:
maj = equal(c, 0) ? '' : `${coeffD}`
break
default:
maj = equal(c, 0) ? '' : `${coeffD}x^${i}`
}
break
}
case 0:
maj = equal(c, 0) ? '' : ecritureAlgebrique(c)
break
case 1:
maj = equal(c, 0) ? '' : `${ecritureAlgebriqueSauf1(c)}x`
break
default:
maj = equal(c, 0) ? '' : `${ecritureAlgebriqueSauf1(c)}x^${i}`
break
}
maj = maj.replace(/\s/g, '').replace(',', '.')
res = maj + res
}
return res
}
/**
* @param {boolean} alg si true alors le coefficient dominant est doté de son signe +/-
* @returns {string} expression mathématique
*/
toLatex (alg = false) {
let res = ''
let maj = ''
for (const [i, c] of this.monomes.entries()) {
switch (i) {
case this.deg: {
const coeffD = alg ? ecritureAlgebriqueSauf1(this.isUseFraction ? fraction(c) : c) : this.deg === 0 ? (this.isUseFraction ? fraction(c).toLatex() : c) : rienSi1(this.isUseFraction ? fraction(c) : c)
switch (this.deg) {
case 1:
maj = equal(c, 0) ? '' : `${coeffD}x`
break
case 0:
maj = equal(c, 0) ? '' : `${coeffD}`
break
default:
maj = equal(c, 0) ? '' : `${coeffD}x^${i}`
}
break
}
case 0:
maj = equal(c, 0) ? '' : ecritureAlgebrique(this.isUseFraction ? fraction(c) : c)
break
case 1:
maj = equal(c, 0) ? '' : `${ecritureAlgebriqueSauf1(this.isUseFraction ? fraction(c) : c)}x`
break
default:
maj = equal(c, 0) ? '' : `${ecritureAlgebriqueSauf1(this.isUseFraction ? fraction(c) : c)}x^${i}`
break
}
res = maj + res
}
return res
}
/**
* Polynome type conversion to String
* @returns le résultat de toMathExpr()
*/
toString () {
return this.toLatex()
}
/**
* Ajoute un Polynome ou une constante
* @param {Polynome|number|Fraction} p
* @example p.add(3) pour ajouter la constante 3 à p
* @returns {Polynome} this+p
*/
add (p) {
const isUseFraction = this.isUseFraction
if (typeof p === 'number' || p.type === 'Fraction') {
const coeffs = this.monomes
coeffs[0] = add(this.monomes[0], p)
return new Polynome({ isUseFraction, coeffs })
} else if (p instanceof Polynome) {
const degSomme = max(this.deg, p.deg)
const pInf = equal(p.deg, degSomme) ? this : p
const pSup = equal(p.deg, degSomme) ? p : this
const coeffSomme = isUseFraction
? pSup.monomes.map(function (el, index) { return index <= pInf.deg ? fraction(add(el, pInf.monomes[index])) : fraction(el) })
: pSup.monomes.map(function (el, index) { return index <= pInf.deg ? add(el, pInf.monomes[index]) : el })
return new Polynome({ isUseFraction, coeffs: coeffSomme })
} else {
window.notify('Polynome.add(arg) : l\'argument n\'est ni un nombre, ni un polynome', { p })
}
}
/**
*
* @param {Polynome|number|Fraction} q Polynome, nombre ou fraction
* @example poly = poly.multiply(fraction(1,3)) divise tous les coefficients de poly par 3.
* @returns q fois this
*/
multiply (q) {
const isUseFraction = this.isUseFraction
let coeffs
if (typeof q === 'number' || q.type === 'Fraction') {
coeffs = isUseFraction
? this.monomes.map(function (el, i) { return fraction(multiply(el, q)) })
: this.monomes.map(function (el, i) { return multiply(el, q) })
} else if (q instanceof Polynome) {
coeffs = new Array(this.deg + q.deg + 1)
coeffs.fill(0)
for (let i = 0; i <= this.deg; i++) {
for (let j = 0; j <= q.deg; j++) {
coeffs[i + j] = add(coeffs[i + j], multiply(this.monomes[i], q.monomes[j]))
}
}
} else {
window.notify('Polynome.multiply(arg) : l\'argument n\'est ni un nombre, ni un polynome', { q })
}
return new Polynome({ isUseFraction, coeffs })
}
/**
* Retourne la dérivée
* @returns {Polynome} dérivée de this
*/
derivee () {
const coeffDerivee = this.isUseFraction
? this.monomes.map(function (el, i) { return fraction(multiply(i, el)) })
: this.monomes.map(function (el, i) { return multiply(i, el) })
coeffDerivee.shift()
return new Polynome({ isUseFraction: this.isUseFraction, coeffs: coeffDerivee })
}
/**
* Appelle toMathExpr
* @param {Array} coeffs coefficients du polynôme par ordre de degré croissant
* @param {boolean} alg si true alors le coefficient dominant est doté de son signe +/-
* @returns {string} expression du polynome
*/
static print (coeffs, alg = false) {
const p = new Polynome({ coeffs })
return p.toLatex(alg)
}
/**
* la fonction à utiliser pour tracer la courbe par exemple ou calculer des valeurs comme dans pol.image()
* const f = pol.fonction est une fonction utilisable dans courbe()
* @returns {function(number): number}
*/
get fonction () {
return x => this.monomes.reduce((val, current) => val + current * x ** this.monomes.findIndex(coeff => coeff === current))
}
/**
* Pour calculer l'image d'un nombre
* @param x
* @returns {math.Fraction | number | int} // à mon avis ça ne retourne que des number...
*/
image (x) {
return this.fonction(x)
}
}
function angleOppose (angle) { // retourne l'angle opposé d'un angle du premier cadrant (sinon, on pourrait avoir plusieurs signe '-' collés ensemble)
if (angle.degres === '0') {
return angle
} else {
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 })
}
}
function complementaireRad (angleEnRadian) { // retourne la mesure en radians du complémentaire d'un angle du premier quadrant donné également en radians
switch (angleEnRadian) {
case '\\dfrac{\\pi}{4}':
return angleEnRadian
case '\\dfrac{\\pi}{6}':
return '\\dfrac{\\pi}{3}'
case '\\dfrac{\\pi}{3}':
return '\\dfrac{\\pi}{6}'
case '\\dfrac{\\pi}{2}' :
return '0'
case '0' :
return '\\dfrac{\\pi}{2}'
}
}
function supplementaireRad (angleEnRadian) { // retourne la mesure en radians du supplémentaire d'un angle du premier quadrant donné également en radians
switch (angleEnRadian) {
case '\\dfrac{\\pi}{4}':
return '\\dfrac{3\\pi}{4}'
case '\\dfrac{\\pi}{6}':
return '\\dfrac{5\\pi}{6}'
case '\\dfrac{\\pi}{3}':
return '\\dfrac{2\\pi}{3}'
case '\\dfrac{\\pi}{2}' :
return '\\dfrac{\\pi}{2}'
case '0' :
return '\\pi'
}
}
function inverseTan (angle) {
switch (angle.tan) {
case '\\infin':
case '-\\infin':
return '0'
case '1': return '1'
case '\\sqrt{3}': return '\\dfrac{\\sqrt{3}}{3}'
case '\\dfrac{\\sqrt{3}}{3}': return '\\sqrt{3}'
}
}
function angleComplementaire (angle) { // retourne l'angle complémentaire d'un angle du premier cadrant
return new Angle({ degres: (90 - parseInt(angle.degres)).toString(), cos: angle.sin, sin: angle.cos, tan: inverseTan(angle), radians: complementaireRad(angle.radians) })
}
function angleSupplementaire (angle) { // retourne l'angle supplémentaire d'un angle du premier cadrant
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) })
}
function opposeStringArray (value) {
if (Array.isArray(value)) {
const result = []
for (const e of value) {
result.push('-' + e)
}
return result
} else return '-' + value
}
/**
* @class
* Crée un objet qui contient les propriétés suivantes : degres, cos, sin, tan et radians.
*/
export class Angle {
/**
* @constructor
* @param {object} param
* @param {string} [param.degres] mesure de l'angle en degrés sous forme de string
* @param {string} [param.cos] valeur du cosinus sous forme de string
* @param {string} [param.sin] valeur du sinus sous forme de string
* @param {string} [param.tan] valeur de la tangente sous forme de string
* @param {string} [param.radians] mesure de l'angle en radians sous forme de string
* @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}'}
* @author Jean-Claude Lhote
*/
constructor ({ degres, cos, sin, tan, radians }) { // il faut au moins fournir la mesure en degrés
this.degres = degres
const anglesDeBase = [
{ degres: '90', cos: '0', sin: '1', tan: '\\infin', radians: '\\dfrac{\\pi}{2}' },
{ degres: '45', cos: '\\dfrac{\\sqrt{2}}{2}', sin: '\\dfrac{\\sqrt{2}}{2}', tan: '1', radians: '\\dfrac{\\pi}{4}' },
{ degres: '60', cos: ['\\dfrac{1}{2}', '0.5'], sin: '\\dfrac{\\sqrt{3}}{2}', tan: '\\sqrt{3}', radians: '\\dfrac{\\pi}{3}' },
{ degres: '30', sin: ['\\dfrac{1}{2}', '0.5'], cos: '\\dfrac{\\sqrt{3}}{2}', tan: '\\dfrac{\\sqrt{3}}{3}', radians: '\\dfrac{\\pi}{6}' },
{ degres: '0', cos: '1', sin: '0', tan: '0', radians: '0' }
]
const angle = anglesDeBase.find(el => el.degres === (parseInt(degres) % 360).toString())
if (angle === undefined) { // si ce n'est pas un des anglesDeBase, alors il faut les autres arguments.
this.cos = cos
this.sin = sin
this.tan = tan
this.radians = radians
} 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
this.cos = cos || angle.cos
this.sin = sin || angle.sin
this.tan = tan || angle.tan
this.radians = radians || angle.radians
}
}
}
export const anglesDeBase = [
new Angle({ degres: '90', cos: '0', sin: '1', tan: '\\infin', radians: '\\dfrac{\\pi}{2}' }),
new Angle({ degres: '45', cos: '\\dfrac{\\sqrt{2}}{2}', sin: '\\dfrac{\\sqrt{2}}{2}', tan: '1', radians: '\\dfrac{\\pi}{4}' }),
new Angle({ degres: '60', cos: ['\\dfrac{1}{2}', '0.5'], sin: '\\dfrac{\\sqrt{3}}{2}', tan: '\\sqrt{3}', radians: '\\dfrac{\\pi}{3}' }),
new Angle({ degres: '30', sin: ['\\dfrac{1}{2}', '0.5'], cos: '\\dfrac{\\sqrt{3}}{2}', tan: '\\dfrac{\\sqrt{3}}{3}', radians: '\\dfrac{\\pi}{6}' }),
new Angle({ degres: '0', cos: '1', sin: '0', tan: '0', radians: '0' })
]
function moduloDeg (angleEnDegre, k) {
const coef = 360 / parseInt(angleEnDegre)
if (angleEnDegre === '0') {
return ((2 * k) * 180).toString()
} else return ((coef * k + 1) * parseInt(angleEnDegre)).toString()
}
function moduloRad (angleEnDegre, k) {
const coef = 360 / parseInt(angleEnDegre)
if (angleEnDegre === '0') {
return `${2 * k}\\pi`
} else return `\\dfrac{${coef * k + 1}\\pi}{${coef / 2}}`
}
/**
*
* @param {Angle} angle
* @param {number} k On part de l'objet angle et on ajoute 2 * k * pi
* @returns {Angle}
*/
function angleModulo (angle, k) {
return new Angle({ degres: moduloDeg(angle.degres, k), cos: angle.cos, sin: angle.sin, tan: angle.tan, radians: moduloRad(angle.degres, k) })
}
/**
* @param {object} param
* @param {boolean} [param.associes] false pour niveau1 (quart de cercle) uniquement, true pour ajouter niveau2 (cercle trigo)
* @param {number[]} [param.modulos] liste des k à utiliser pour ajouter les angles modulo 2k*Pi
* @returns {{liste1: string[], liste2: string[], liste3: string[]}} liste1, liste2, liste3 les listes (niveau2 contient niveau1 et niveau3 contient niveau2)
* @author Jean-Claude Lhote
*/
export function valeursTrigo ({ associes = true, modulos = [-1, 1] }) {
let mesAngles = anglesDeBase.slice()
const mesAnglesNiv1 = mesAngles.slice()
const nombreAnglesDeBase = mesAngles.length
// ici on complète la liste avec tous les angles associés en faisant attention de ne pas ajouter deux fois les mêmes.
for (let i = 0; i < nombreAnglesDeBase; i++) {
mesAngles.push(angleOppose(mesAngles[i]), angleComplementaire(mesAngles[i]), angleSupplementaire(mesAngles[i]))
}
// On supprime les doublons en comparant la mesure en degrés
mesAngles = [...new Map(mesAngles.map(item => [item.degres, item])).values()]
const mesAnglesNiv2 = mesAngles.slice()
for (let i = 0; i < nombreAnglesDeBase; i++) {
for (const k of modulos) {
if (k !== 0) mesAngles.push(angleModulo(mesAngles[i % nombreAnglesDeBase], k))
}
}
const mesAnglesNiv3 = mesAngles.slice()
return { liste1: mesAnglesNiv1, liste2: mesAnglesNiv2, liste3: mesAnglesNiv3 }
}