Imperfect implementation, but working sequences.

This commit is contained in:
Adsooi 2020-12-26 19:16:42 +01:00
parent 7e47b3cdf9
commit 422aa5b4c7
6 changed files with 205 additions and 65 deletions

View file

@ -71,7 +71,7 @@ Column {
if(value.toString()=="NaN")
value = ""
}
if(value != "" && valueInput.acceptableInput) {
if(value !== "" && valueInput.acceptableInput) {
control.model.setProperty(index, 'key', value)
control.changed()
}
@ -111,7 +111,7 @@ Column {
if(value.toString()=="NaN")
value = ""
}
if(value != "" && keyInput.acceptableInput) {
if(value !== "" && keyInput.acceptableInput) {
control.model.setProperty(index, 'val', value)
control.changed()
}
@ -153,6 +153,7 @@ Column {
var ret = []
for(var i = 0; i < model.count; i++)
ret.push(model.get(i).val)
return ret
}
}
}

View file

@ -128,13 +128,12 @@ Canvas {
drawVisibleText(ctx, "10"+Utils.textsup(xpow), x2px(Math.pow(10,xpow))-textSize/2, axisxpx+16+(6*(y==0)))
}
} else {
for(var x = 0; x < 40*maxgradx; x += 1) {
var drawX = x*yaxisstep1
var txtX = yaxisstepExpr.simplify(x)
for(var x = 1; x < drawMaxX; x += 1) {
var drawX = x*xaxisstep1
var txtX = xaxisstepExpr.simplify(x)
var textSize = measureText(ctx, txtX, 6).height
if(x != 0)
drawVisibleText(ctx, txtX, x2px(drawX)-4, axisxpx+6+textSize)
drawVisibleText(ctx, '-'+txtX, x2px(-drawX)-4, axisxpx+6+textSize)
drawVisibleText(ctx, txtX, x2px(drawX)-4, axisxpx+6+textSize)
drawVisibleText(ctx, '-'+txtX, x2px(-drawX)-4, axisxpx+6+textSize)
}
}
for(var y = 0; y < drawMaxY; y += 1) {

View file

@ -359,6 +359,7 @@ ListView {
onChanged: {
Objects.currentObjects[objEditor.objType][objEditor.objIndex][modelData[0]] = exportModel()
Objects.currentObjects[objEditor.objType][objEditor.objIndex].update()
objectListList.update()
}

View file

@ -24,7 +24,6 @@
const parser = new ExprEval.Parser()
var u = {1: 1, 2: 2, 3: 3}
console.log(parser.parse('u[n]+u[n+1]+u[n+2]').simplify().evaluate({"u": u, 'n': 1}))
var evalVariables = { // Variables not provided by expr-eval.js, needs to be provided manualy
"pi": Math.PI,
@ -45,7 +44,7 @@ class Expression {
}
isConstant() {
return this.expr.indexOf("x") == -1
return !this.expr.includes("x") && !this.expr.includes("n")
}
execute(x = 1) {
@ -81,33 +80,69 @@ function executeExpression(expr){
class Sequence extends Expression {
constructor(name, baseValues = {}, valuePlus = 1, expr = "") {
// u[n+valuePlus] = expr
console.log('Expression', expr)
super(expr)
this.name = name
this.baseValues = baseValues
this.valuePlus = valuePlus
this.calcValues = Object.assign({}, baseValues)
for(var n in this.calcValues)
if(['string', 'number'].includes(typeof this.calcValues[n]))
this.calcValues[n] = parser.parse(this.calcValues[n].toString()).simplify()
this.valuePlus = parseInt(valuePlus)
}
isConstant() {
return this.expr.indexOf("n") == -1
}
execute(n = 0) {
if(this.cached) return this.cachedValue
if(n in this.baseValues) return this.baseValues[n]
var vars = Object.assign({'n': n-this.valuePlus}, evalVariables)
vars[this.name] = this.baseValues
var un = this.calc.evaluate(vars)
this.baseValues[n] = un
return un
execute(n = 1) {
if(n in this.calcValues)
return this.calcValues[n].evaluate(evalVariables)
this.cache(n)
return this.calcValues[n].evaluate(evalVariables)
}
simplify(n = 1) {
if(n in this.calcValues)
return Utils.makeExpressionReadable(this.calcValues[n].toString())
this.cache(n)
return Utils.makeExpressionReadable(this.calcValues[n].toString())
}
cache(n = 1) {
var str = this.calc.substitute('n', n-this.valuePlus).toString()
for(var newn in this.calcValues) {
var un = new RegExp(`${this.name}\\[${newn}\\]`, 'g')
if(un.test(str)) {
if (this.calcValues[newn] == undefined)
this.cache(newn)
str = str.replace(un, this.calcValues[newn])
}
}
var expr = parser.parse(str).simplify()
if(expr.evaluate(evalVariables) == 0) expr = parser.parse('0')
console.log(n, expr.toString())
expr = parser.parse(Utils.simplifyExpression(expr.toString())).simplify()
this.calcValues[n] = expr
}
toString(forceSign=false) {
var str = Utils.makeExpressionReadable(this.calc.toString())
if(str[0] != '-' && forceSign) str = '+' + str
var subtxt = this.valuePlus == 0 ? 'ₙ' : Utils.textsub('n+' + this.valuePlus)
var ret = `${this.name}${subtxt} = ${str}${this.baseValues.length == 0 ? '' : "\n"}`
ret += Object.keys(this.baseValues).map(
n => `${this.name}${Utils.textsub(n)} = ${this.baseValues[n]}`
).join('; ')
return ret
}
}
var test = new Sequence('u', {0: 0, 1: 1}, 2, '3*u[n]+3')
var test = new Sequence('u', {0: '0', 1: 'π'}, 2, '3*u[n]')
console.log(test)
for(var i=0; i<20; i++)
for(var i=0; i<20; i++) {
//console.log('u' + Utils.textsub(i) + ' = ' + test.simplify(i))
console.log('u' + Utils.textsub(i) + ' = ' + test.execute(i))
}
// Domains
class Domain {
constructor() {}

View file

@ -45,10 +45,10 @@ class DrawableObject {
// or are instanciated by other objects.
static createable() {return true}
// Properties are set with key as property name and
// value as it's type name (e.g 'Expression', 'string',
// 'Point'...), an Array for enumerations,
// a List instance for lists, a Dictionary instance for
// dictionary
// value as it's type name (e.g 'Expression', 'string'...),
// an Enum for enumerations, an ObjectType for DrawableObjects
// with a specific type, a List instance for lists, a
// Dictionary instance for dictionaries...
// Used for property modifier in the sidebar.
static properties() {return {}}
@ -206,12 +206,16 @@ class Function extends ExecutableObject {
'comment1': 'Ex: R+* (ℝ⁺*), N* (*), Z-* (ℤ⁻*), ]0;1[, {3;4;5}',
'labelPosition': new P.Enum('above', 'below'),
'displayMode': new P.Enum('application', 'function'),
'labelX': 'number'
'labelX': 'number',
'comment1': 'The following parameters are used in case of non-continuous ensembles\n(E.g: , , sets like {0;3}...)',
'drawPoints': 'Boolean',
'drawDashedLines': 'Boolean'
}}
constructor(name = null, visible = true, color = null, labelContent = 'name + value',
expression = 'x', inDomain = 'RPE', outDomain = 'R',
displayMode = 'application', labelPosition = 'above', labelX = 1) {
displayMode = 'application', labelPosition = 'above', labelX = 1,
drawPoints = true, drawDashedLines = true) {
if(name == null) name = getNewName('fghjqlmnopqrstuvwabcde')
super(name, visible, color, labelContent)
this.type = 'Function'
@ -224,6 +228,8 @@ class Function extends ExecutableObject {
this.displayMode = displayMode
this.labelPosition = labelPosition
this.labelX = labelX
this.drawPoints = drawPoints
this.drawDashedLines = drawDashedLines
}
getReadableString() {
@ -237,7 +243,7 @@ class Function extends ExecutableObject {
export() {
return [this.name, this.visible, this.color.toString(), this.labelContent,
this.expression.toEditableString(), this.inDomain.toString(), this.outDomain.toString(),
this.displayMode, this.labelPosition, this.labelX]
this.displayMode, this.labelPosition, this.labelX, this.drawPoints, this.drawDashedLines]
}
execute(x = 1) {
@ -257,13 +263,13 @@ class Function extends ExecutableObject {
}
draw(canvas, ctx) {
Function.drawFunction(canvas, ctx, this.expression, this.inDomain, this.outDomain)
Function.drawFunction(canvas, ctx, this.expression, this.inDomain, this.outDomain, this.drawPoints, this.drawDashedLines)
// Label
var text = this.getLabel()
ctx.font = "14px sans-serif"
var textSize = canvas.measureText(ctx, text, 7)
var posX = canvas.x2px(this.labelX)
var posY = canvas.y2px(this.expression.execute(this.labelX))
var posY = canvas.y2px(this.execute(this.labelX))
switch(this.labelPosition) {
case 'above':
canvas.drawVisibleText(ctx, text, posX-textSize.width, posY-textSize.height)
@ -275,32 +281,39 @@ class Function extends ExecutableObject {
}
}
static drawFunction(canvas, ctx, expr, inDomain, outDomain) {
static drawFunction(canvas, ctx, expr, inDomain, outDomain, drawPoints = true, drawDash = true) {
// Reusable in other objects.
// Drawing small traits every 2px
var pxprecision = 2
var previousX = canvas.px2x(0)
var previousY;
if(inDomain instanceof MathLib.SpecialDomain && inDomain.moveSupported) {
// Point based functions.
previousX = inDomain.previous(previousX)
if(previousX === null) previousX = inDomain.next(canvas.px2x(0))
previousY = expr.execute(previousX)
if(!drawPoints && !drawDash) return
while(previousX !== null && canvas.x2px(previousX) < canvas.canvasSize.width) {
var currentX = inDomain.next(previousX)
var currentY = expr.execute(currentX)
if(currentX === null) break;
if((inDomain.includes(currentX) || inDomain.includes(previousX)) &&
(outDomain.includes(currentY) || outDomain.includes(previousY))) {
canvas.drawDashedLine(ctx, canvas.x2px(previousX), canvas.y2px(previousY), canvas.x2px(currentX), canvas.y2px(currentY))
ctx.fillRect(canvas.x2px(previousX)-5, canvas.y2px(previousY)-1, 10, 2)
ctx.fillRect(canvas.x2px(previousX)-1, canvas.y2px(previousY)-5, 2, 10)
if(drawDash)
canvas.drawDashedLine(ctx, canvas.x2px(previousX), canvas.y2px(previousY), canvas.x2px(currentX), canvas.y2px(currentY))
if(drawPoints) {
ctx.fillRect(canvas.x2px(previousX)-5, canvas.y2px(previousY)-1, 10, 2)
ctx.fillRect(canvas.x2px(previousX)-1, canvas.y2px(previousY)-5, 2, 10)
}
}
previousX = currentX
previousY = currentY
}
// Drawing the last cross
ctx.fillRect(canvas.x2px(previousX)-5, canvas.y2px(previousY)-1, 10, 2)
ctx.fillRect(canvas.x2px(previousX)-1, canvas.y2px(previousY)-5, 2, 10)
if(drawPoints) {
// Drawing the last cross
ctx.fillRect(canvas.x2px(previousX)-5, canvas.y2px(previousY)-1, 10, 2)
ctx.fillRect(canvas.x2px(previousX)-1, canvas.y2px(previousY)-5, 2, 10)
}
} else {
previousY = expr.execute(previousX)
for(var px = pxprecision; px < canvas.canvasSize.width; px += pxprecision) {
@ -843,6 +856,7 @@ class CursorX extends DrawableObject {
}
getTargetElement() {
// TODO: Use the dependency system instead.
var elementTypes = Object.keys(currentObjects).filter(objType => types[objType].prototype instanceof ExecutableObject)
return getObjectByName(this.targetElement, elementTypes)
}
@ -922,17 +936,65 @@ class Sequence extends ExecutableObject {
static type(){return 'Sequence'}
static typeMultiple(){return 'Sequences'}
static properties() {return {
'defaultExpression': new P.Dictionary('string', 'int', /^.+$/, /^(\d+)$/, '{name}[n+', '] = ', true),
'drawPoints': 'Boolean',
'drawDashedLines': 'Boolean',
'defaultExpression': new P.Dictionary('string', 'int', /^.+$/, /^\d+$/, '{name}[n+', '] = ', true),
'comment1': 'Note: Use {name}[n] to refer to {name}ₙ, {name}[n+1] for {name}ₙ₊₁...',
'markedValues': new P.Dictionary('string', 'int', /^.+$/, /^(\d+)$/, '{name}[', '] = '),
'baseValues': new P.Dictionary('string', 'int', /^.+$/, /^\d+$/, '{name}[', '] = '),
}}
constructor(name = null, visible = true, color = null, labelContent = 'name + value',
defaultExp = {1: "u[n]"}, markedValues = {0: 0}) {
drawPoints = true, drawDashedLines = true, defaultExp = {1: "n"},
baseValues = {0: 0}) {
if(name == null) name = getNewName('uvwPSUVWabcde')
super(name, visible, color, labelContent)
this.drawPoints = drawPoints
this.drawDashedLines = drawDashedLines
this.defaultExpression = defaultExp
this.markedValues = markedValues
this.baseValues = baseValues
this.update()
}
export() {
return [this.name, this.visible, this.color.toString(), this.labelContent,
this.drawPoints, this.drawDashedLines, this.defaultExpression, this.baseValues]
}
update() {
super.update()
if(
this.sequence == null || this.baseValues != this.sequence.baseValues ||
this.sequence.name != this.name ||
this.sequence.expr != Object.values(this.defaultExpression)[0] ||
this.sequence.valuePlus != Object.keys(this.defaultExpression)[0]
)
this.sequence = new MathLib.Sequence(
this.name, this.baseValues,
Object.keys(this.defaultExpression)[0],
Object.values(this.defaultExpression)[0]
)
}
getReadableString() {
return this.sequence.toString()
}
execute(x = 1) {
if(x % 1 == 0)
return this.sequence.execute(x)
return null
}
canExecute(x = 1) {return x%1 == 0}
// Simplify returns the simplified string of the expression.
simplify(x = 1) {
if(x % 1 == 0)
return this.sequence.simplify(x)
return null
}
draw(canvas, ctx) {
Function.drawFunction(canvas, ctx, this.sequence, canvas.logscalex ? MathLib.Domain.NE : MathLib.Domain.N, MathLib.Domain.R, this.drawPoints, this.drawDashedLines)
}
}

View file

@ -20,6 +20,9 @@
var powerpos = {
"-": "⁻",
"+": "⁺",
"=": "⁼",
" ": "",
"0": "⁰",
"1": "¹",
"2": "²",
@ -30,8 +33,6 @@ var powerpos = {
"7": "⁷",
"8": "⁸",
"9": "⁹",
"+": "⁺",
"=": "⁼",
"a": "ᵃ",
"b": "ᵇ",
"c": "ᶜ",
@ -56,11 +57,14 @@ var powerpos = {
"w": "ʷ",
"x": "ˣ",
"y": "ʸ",
"z": "ᶻ",
"z": "ᶻ"
}
var indicepos = {
"-": "₋",
"+": "₊",
"=": "₌",
" ": "",
"0": "₀",
"1": "₁",
"2": "₂",
@ -71,8 +75,6 @@ var indicepos = {
"7": "₇",
"8": "₈",
"9": "₉",
"+": "₊",
"=": "₌",
"a": "ₐ",
"e": "ₑ",
"h": "ₕ",
@ -122,12 +124,46 @@ function textsub(text) {
function simplifyExpression(str) {
var replacements = [
// Operations not done by parser.
[// Decomposition way 2
/(^.?|[+-] |\()([-.\d\w]+) ([*/]) \((([-.\d\w] [*/] )?[-\d\w.]+) ([+\-]) (([-.\d\w] [*/] )?[\d\w.+]+)\)(.?$| [+-]|\))/g,
"$1$2 $3 $4 $6 $2 $3 $7$9"
],
[ // Decomposition way 2
/(^.?|[+-] |\()\((([-.\d\w] [*/] )?[-\d\w.]+) ([+\-]) (([-.\d\w] [*/] )?[\d\w.+]+)\) ([*/]) ([-.\d\w]+)(.?$| [+-]|\))/g,
"$1$8 $7 $2 $4 $8 $7 $5$9"
],
[ // Factorisation of π elements.
/(([-\d\w.]+ [*/] )*)(pi|π)(( [/*] [-\d\w.]+)*) ([+-]) (([-\d\w.]+ [*/] )*)(pi|π)(( [/*] [-\d\w.]+)*)?/g,
function(match, m1, n1, pi1, m2, ope2, n2, opeM, m3, n3, pi2, m4, ope4, n4) {
// g1, g2, g3 , g4, g5, g6, g7, g8, g9, g10, g11,g12 , g13
// We don't care about mx & pix, ope2 & ope4 are either / or * for n2 & n4.
// n1 & n3 are multiplied, opeM is the main operation (- or +).
// Putting all n in form of number
//n2 = n2 == undefined ? 1 : parseFloat(n)
n1 = m1 == undefined ? 1 : eval(m1 + '1')
n2 = m2 == undefined ? 1 : eval('1' + m2)
n3 = m3 == undefined ? 1 : eval(m3 + '1')
n4 = m4 == undefined ? 1 : eval('1' + m4)
//var [n1, n2, n3, n4] = [n1, n2, n3, n4].map(n => n == undefined ? 1 : parseFloat(n))
// Falling back to * in case it does not exist (the corresponding n would be 1)
var [ope2, ope4] = [ope2, ope4].map(ope => ope == '/' ? '/' : '*')
var coeff1 = n1*n2
var coeff2 = n3*n4
var coefficient = coeff1+coeff2-(opeM == '-' ? 2*coeff2 : 0)
return `${coefficient} * π`
}
],
[ // Removing parenthesis when content is only added from both sides.
/(^.?|[+-] |\()\(([^)(]+)\)(.?$| [+-]|\))/g,
function(match, b4, middle, after) {return `${b4}${middle}${after}`}
],
[ // Removing parenthesis when content is only multiplied.
/(^.?|[*\/] |\()\(([^)(+-]+)\)(.?$| [*\/]|\))/g,
/(^.?|[*\/] |\()\(([^)(+-]+)\)(.?$| [*\/+-]|\))/g,
function(match, b4, middle, after) {return `${b4}${middle}${after}`}
],
[ // Removing parenthesis when content is only multiplied.
/(^.?|[*\/-+] |\()\(([^)(+-]+)\)(.?$| [*\/]|\))/g,
function(match, b4, middle, after) {return `${b4}${middle}${after}`}
],
[// Simplification additions/substractions.
@ -176,26 +212,33 @@ function simplifyExpression(str) {
}
],
// Simple simplifications
[/(\s|^|\()0 \* (\([^)(]+\))/g, '$10'],
[/(\s|^|\()0 \* ([^)(+-]+)/g, '$10'],
[/(\([^)(]\)) \* 0(\s|$|\))/g, '0$2'],
[/([^)(+-]) \* 0(\s|$|\))/g, '0$2'],
[/(\s|^|\()1 (\*|\/) /g, '$1'],
[/(\s|^|\()0 (\+|\-) /g, '$1'],
[/ (\*|\/) 1(\s|$|\))/g, '$2'],
[/ (\+|\-) 0(\s|$|\))/g, '$2'],
[/(\s|^|\()0(\.0+)? \* (\([^)(]+\))/g, '$10'],
[/(\s|^|\()0(\.0+)? \* ([^)(+-]+)/g, '$10'],
[/(\([^)(]\)) \* 0(\.0+)?(\s|$|\))/g, '0$3'],
[/([^)(+-]) \* 0(\.0+)?(\s|$|\))/g, '0$3'],
[/(\s|^|\()1(\.0+)? (\*|\/) /g, '$1'],
[/(\s|^|\()0(\.0+)? (\+|\-) /g, '$1'],
[/ (\*|\/) 1(\.0+)?(\s|$|\))/g, '$3'],
[/ (\+|\-) 0(\.0+)?(\s|$|\))/g, '$3'],
[/(^| |\() /g, '$1'],
[/ ($|\))/g, '$1'],
]
// Replacements
replacements.forEach(function(replacement){
while(replacement[0].test(str))
str = str.replace(replacement[0], replacement[1])
})
var found
do {
found = false
for(var replacement of replacements)
while(replacement[0].test(str)) {
found = true
str = str.replace(replacement[0], replacement[1])
}
} while(found)
return str
}
console.log(simplifyExpression("(4 * (4 * pi + pi)) + pi"))
function makeExpressionReadable(str) {
var replacements = [
// variables
@ -209,6 +252,7 @@ function makeExpressionReadable(str) {
[/\^([^ ]+)/g, function(match, p1) { return textsup(p1) }],
[/_\(([^_]+)\)/g, function(match, p1) { return textsub(p1) }],
[/_([^ ]+)/g, function(match, p1) { return textsub(p1) }],
[/\[([^\[\]]+)\]/g, function(match, p1) { return textsub(p1) }],
[/(\d|\))×/g, '$1'],
//[/×(\d|\()/g, '$1'],
[/\(([^)(+.\/-]+)\)/g, "$1"],
@ -216,10 +260,9 @@ function makeExpressionReadable(str) {
str = simplifyExpression(str)
// Replacements
replacements.forEach(function(replacement){
for(var replacement of replacements)
while(replacement[0].test(str))
str = str.replace(replacement[0], replacement[1])
})
return str
}
@ -269,9 +312,8 @@ function parseName(str, removeUnallowed = true) {
]
if(!removeUnallowed) replacements.pop()
// Replacements
replacements.forEach(function(replacement){
for(var replacement of replacements)
str = str.replace(replacement[0], replacement[1])
})
return str
}