diff --git a/common/src/math/domain.mjs b/common/src/math/domain.mjs index 48bf8ad..19586ee 100644 --- a/common/src/math/domain.mjs +++ b/common/src/math/domain.mjs @@ -24,6 +24,7 @@ import { Expression, executeExpression } from "./expression.mjs" */ export class Domain { constructor() { + this.latexMarkup = "#INVALID" } /** @@ -206,8 +207,8 @@ export class Range extends Domain { } includes(x) { - if(x instanceof Expression) x = x.execute() if(typeof x == "string") x = executeExpression(x) + if(x instanceof Expression) x = x.execute() return ((this.openBegin && x > this.begin.execute()) || (!this.openBegin && x >= this.begin.execute())) && ((this.openEnd && x < this.end.execute()) || (!this.openEnd && x <= this.end.execute())) } @@ -249,15 +250,17 @@ export class SpecialDomain extends Domain { /** * @constructs SpecialDomain * @param {string} displayName + * @param {string} latexMarkup - markup representing the domain. * @param {function} isValid - function returning true when number is in domain false when it isn't. * @param {function} next - function provides the next positive value in the domain after the one given. * @param {function} previous - function provides the previous positive value in the domain before the one given. * @param {boolean} moveSupported - Only true if next and previous functions are valid. */ - constructor(displayName, isValid, next = () => true, previous = () => true, + constructor(displayName, latexMarkup, isValid, next = () => true, previous = () => true, moveSupported = true) { super() this.displayName = displayName + this.latexMarkup = latexMarkup this.isValid = isValid this.nextValue = next this.prevValue = previous @@ -562,39 +565,54 @@ Domain.RPE.latexMarkup = "\\mathbb{R}^{+*}" Domain.RME = new Range(-Infinity, 0, true, true) Domain.RME.displayName = "ℝ⁻*" Domain.RME.latexMarkup = "\\mathbb{R}^{+*}" -Domain.N = new SpecialDomain("ℕ", x => x % 1 === 0 && x >= 0, +Domain.N = new SpecialDomain( + "ℕ", "\\mathbb{N}", + x => x % 1 === 0 && x >= 0, x => Math.max(Math.floor(x) + 1, 0), - x => Math.max(Math.ceil(x) - 1, 0)) -Domain.N.latexMarkup = "\\mathbb{N}" -Domain.NE = new SpecialDomain("ℕ*", x => x % 1 === 0 && x > 0, + x => Math.max(Math.ceil(x) - 1, 0) +) +Domain.NE = new SpecialDomain( + "ℕ*", "\\mathbb{N}^{*}", + x => x % 1 === 0 && x > 0, x => Math.max(Math.floor(x) + 1, 1), - x => Math.max(Math.ceil(x) - 1, 1)) -Domain.NE.latexMarkup = "\\mathbb{N}^{*}" -Domain.Z = new SpecialDomain("ℤ", x => x % 1 === 0, x => Math.floor(x) + 1, x => Math.ceil(x) - 1) -Domain.Z.latexMarkup = "\\mathbb{Z}" -Domain.ZE = new SpecialDomain("ℤ*", x => x % 1 === 0 && x !== 0, + x => Math.max(Math.ceil(x) - 1, 1) +) +Domain.Z = new SpecialDomain( + "ℤ", "\\mathbb{Z}", + x => x % 1 === 0, + x => Math.floor(x) + 1, + x => Math.ceil(x) - 1 +) +Domain.ZE = new SpecialDomain( + "ℤ*", "\\mathbb{Z}^{*}", + x => x % 1 === 0 && x !== 0, x => Math.floor(x) + 1 === 0 ? Math.floor(x) + 2 : Math.floor(x) + 1, - x => Math.ceil(x) - 1 === 0 ? Math.ceil(x) - 2 : Math.ceil(x) - 1) -Domain.ZE.latexMarkup = "\\mathbb{Z}^{*}" -Domain.ZM = new SpecialDomain("ℤ⁻", x => x % 1 === 0 && x <= 0, + x => Math.ceil(x) - 1 === 0 ? Math.ceil(x) - 2 : Math.ceil(x) - 1 +) +Domain.ZM = new SpecialDomain( + "ℤ⁻", "\\mathbb{Z}^{-}", + x => x % 1 === 0 && x <= 0, x => Math.min(Math.floor(x) + 1, 0), - x => Math.min(Math.ceil(x) - 1, 0)) -Domain.ZM.latexMarkup = "\\mathbb{Z}^{-}" -Domain.ZME = new SpecialDomain("ℤ⁻*", x => x % 1 === 0 && x < 0, + x => Math.min(Math.ceil(x) - 1, 0) +) +Domain.ZME = new SpecialDomain( + "ℤ⁻*", "\\mathbb{Z}^{-*}", + x => x % 1 === 0 && x < 0, x => Math.min(Math.floor(x) + 1, -1), - x => Math.min(Math.ceil(x) - 1, -1)) -Domain.ZME.latexMarkup = "\\mathbb{Z}^{-*}" -Domain.NLog = new SpecialDomain("ℕˡᵒᵍ", + x => Math.min(Math.ceil(x) - 1, -1) +) +Domain.NLog = new SpecialDomain( + "ℕˡᵒᵍ", "\\mathbb{N}^{log}", x => x / Math.pow(10, Math.ceil(Math.log10(x))) % 1 === 0 && x > 0, - function(x) { + x => { let x10pow = Math.pow(10, Math.ceil(Math.log10(x))) return Math.max(1, (Math.floor(x / x10pow) + 1) * x10pow) }, - function(x) { + x => { let x10pow = Math.pow(10, Math.ceil(Math.log10(x))) return Math.max(1, (Math.ceil(x / x10pow) - 1) * x10pow) - }) -Domain.NLog.latexMarkup = "\\mathbb{N}^{log}" + } +) let refedDomains = [] @@ -626,7 +644,7 @@ export function parseDomainSimple(domain) { if(domain.includes("U") || domain.includes("∪")) return UnionDomain.import(domain) if(domain.includes("∩")) return IntersectionDomain.import(domain) if(domain.includes("∖") || domain.includes("\\")) return MinusDomain.import(domain) - if(domain.charAt(0) === "{" && domain.charAt(domain.length - 1) === "}") return DomainSet.import(domain) + if(domain.at(0) === "{" && domain.at(-1) === "}") return DomainSet.import(domain) if(domain.includes("]") || domain.includes("[")) return Range.import(domain) if(["R", "ℝ", "N", "ℕ", "Z", "ℤ"].some(str => domain.toUpperCase().includes(str))) return Domain.import(domain) diff --git a/common/test/math/domain.mjs b/common/test/math/domain.mjs index e32dbd1..6c2b93e 100644 --- a/common/test/math/domain.mjs +++ b/common/test/math/domain.mjs @@ -19,46 +19,90 @@ import { describe, it } from "mocha" import { expect } from "chai" -// import { Domain, parseDomainSimple } from "../../src/math/domain.mjs" -// -// describe("math.domain", function() { -// describe("#parseDomainSimple", function() { -// it("returns predefined domains", function() { -// const predefinedToCheck = [ -// // Real domains -// { domain: Domain.R, shortcuts: ["R", "ℝ"] }, -// // Zero exclusive real domains -// { domain: Domain.RE, shortcuts: ["RE", "R*", "ℝ*"] }, -// // Real positive domains -// { domain: Domain.RP, shortcuts: ["RP", "R+", "ℝ⁺", "ℝ+"] }, -// // Zero-exclusive real positive domains -// { domain: Domain.RPE, shortcuts: ["RPE", "REP", "R+*", "R*+", "ℝ*⁺", "ℝ⁺*", "ℝ*+", "ℝ+*"] }, -// // Real negative domain -// { domain: Domain.RM, shortcuts: ["RM", "R-", "ℝ⁻", "ℝ-"] }, -// // Zero-exclusive real negative domains -// { domain: Domain.RME, shortcuts: ["RME", "REM", "R-*", "R*-", "ℝ⁻*", "ℝ*⁻", "ℝ-*", "ℝ*-"] }, -// // Natural integers domain -// { domain: Domain.N, shortcuts: ["ℕ", "N", "ZP", "Z+", "ℤ⁺", "ℤ+"] }, -// // Zero-exclusive natural integers domain -// { domain: Domain.NE, shortcuts: ["NE", "NP", "N*", "N+", "ℕ*", "ℕ⁺", "ℕ+", "ZPE", "ZEP", "Z+*", "Z*+", "ℤ⁺*", "ℤ*⁺", "ℤ+*", "ℤ*+"] }, -// // Logarithmic natural domains -// { domain: Domain.NLog, shortcuts: ["NLOG", "ℕˡᵒᵍ", "ℕLOG"] }, -// // All integers domains -// { domain: Domain.Z, shortcuts: ["Z", "ℤ"] }, -// // Zero-exclusive all integers domain -// { domain: Domain.ZE, shortcuts: ["ZE", "Z*", "ℤ*"] }, -// // Negative integers domain -// { domain: Domain.ZM, shortcuts: ["ZM", "Z-", "ℤ⁻", "ℤ-"] }, -// // Zero-exclusive negative integers domain -// { domain: Domain.ZME, shortcuts: ["ZME", "ZEM", "Z-*", "Z*-", "ℤ⁻*", "ℤ*⁻", "ℤ-*", "ℤ*-"] }, -// ] -// -// // Real domains -// for(const { domain, shortcuts } of predefinedToCheck) -// for(const shortcut of shortcuts) -// expect(parseDomainSimple(shortcut)).to.be.equal(domain) -// }) -// -// it("") -// }) -// }) +import { Domain, EmptySet, parseDomainSimple } from "../../src/math/domain.mjs" + +describe("math.domain", function() { + describe("#parseDomainSimple", function() { + it("returns empty sets when a domain cannot be parsed", function() { + expect(parseDomainSimple("∅")).to.be.an.instanceof(EmptySet) + expect(parseDomainSimple("O")).to.be.an.instanceof(EmptySet) + expect(parseDomainSimple("AAAAAAAAA")).to.be.an.instanceof(EmptySet) + expect(parseDomainSimple("???")).to.be.an.instanceof(EmptySet) + expect(parseDomainSimple("∅").latexMarkup).to.equal("\\emptyset") + expect(parseDomainSimple("???").toString()).to.equal("∅") + expect(parseDomainSimple("∅").includes(0)).to.be.false + expect(parseDomainSimple("∅").includes(Infinity)).to.be.false + expect(parseDomainSimple("∅").includes(-3)).to.be.false + + }) + + it("returns predefined domains", function() { + const predefinedToCheck = [ + // Real domains + { domain: Domain.R, shortcuts: ["R", "ℝ"] }, + // Zero exclusive real domains + { domain: Domain.RE, shortcuts: ["RE", "R*", "ℝ*"] }, + // Real positive domains + { domain: Domain.RP, shortcuts: ["RP", "R+", "ℝ⁺", "ℝ+"] }, + // Zero-exclusive real positive domains + { domain: Domain.RPE, shortcuts: ["RPE", "REP", "R+*", "R*+", "ℝ*⁺", "ℝ⁺*", "ℝ*+", "ℝ+*"] }, + // Real negative domain + { domain: Domain.RM, shortcuts: ["RM", "R-", "ℝ⁻", "ℝ-"] }, + // Zero-exclusive real negative domains + { domain: Domain.RME, shortcuts: ["RME", "REM", "R-*", "R*-", "ℝ⁻*", "ℝ*⁻", "ℝ-*", "ℝ*-"] }, + // Natural integers domain + { domain: Domain.N, shortcuts: ["ℕ", "N", "ZP", "Z+", "ℤ⁺", "ℤ+"] }, + // Zero-exclusive natural integers domain + { domain: Domain.NE, shortcuts: ["NE", "NP", "N*", "N+", "ℕ*", "ℕ⁺", "ℕ+", "ZPE", "ZEP", "Z+*", "Z*+", "ℤ⁺*", "ℤ*⁺", "ℤ+*", "ℤ*+"] }, + // Logarithmic natural domains + { domain: Domain.NLog, shortcuts: ["NLOG", "ℕˡᵒᵍ", "ℕLOG"] }, + // All integers domains + { domain: Domain.Z, shortcuts: ["Z", "ℤ"] }, + // Zero-exclusive all integers domain + { domain: Domain.ZE, shortcuts: ["ZE", "Z*", "ℤ*"] }, + // Negative integers domain + { domain: Domain.ZM, shortcuts: ["ZM", "Z-", "ℤ⁻", "ℤ-"] }, + // Zero-exclusive negative integers domain + { domain: Domain.ZME, shortcuts: ["ZME", "ZEM", "Z-*", "Z*-", "ℤ⁻*", "ℤ*⁻", "ℤ-*", "ℤ*-"] }, + ] + + // Real domains + for(const { domain, shortcuts } of predefinedToCheck) + for(const shortcut of shortcuts) + expect(parseDomainSimple(shortcut)).to.be.equal(domain) + }) + + it("returns parsed ranges", function() { + const parsedClosed = parseDomainSimple("[1;3]") + expect(parsedClosed.includes(1)).to.be.true + expect(parsedClosed.includes(2.4)).to.be.true + expect(parsedClosed.includes(3)).to.be.true + expect(parsedClosed.includes(3.01)).to.be.false + expect(parsedClosed.includes(0.99)).to.be.false + const parsedOpen = parseDomainSimple("]1;3[") + expect(parsedOpen.includes(1)).to.be.false + expect(parsedOpen.includes(3)).to.be.false + expect(parsedOpen.includes(2.4)).to.be.true + expect(parsedOpen.includes(1.01)).to.be.true + expect(parsedOpen.includes(2.99)).to.be.true + const parsedOpenBefore = parseDomainSimple("]1;3]") + expect(parsedOpenBefore.includes(1)).to.be.false + expect(parsedOpenBefore.includes(3)).to.be.true + expect(parsedOpenBefore.includes(2.4)).to.be.true + expect(parsedOpenBefore.includes(1.01)).to.be.true + expect(parsedOpenBefore.includes(3.01)).to.be.false + const parsedOpenAfter = parseDomainSimple("[1;3[") + expect(parsedOpenAfter.includes(1)).to.be.true + expect(parsedOpenAfter.includes(3)).to.be.false + expect(parsedOpenAfter.includes(2.4)).to.be.true + expect(parsedOpenAfter.includes(0.99)).to.be.false + expect(parsedOpenAfter.includes(2.99)).to.be.true + }) + + it("does not parse invalid ranges", function() { + expect(() => parseDomainSimple("]1;2;3[")).to.throw + expect(() => parseDomainSimple("]1,2;3[")).to.throw + expect(() => parseDomainSimple("](12);3[")).to.throw + }) + }) +}) diff --git a/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/InsertCharacter.qml b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/InsertCharacter.qml index 7ef1794..9bf8f06 100644 --- a/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/InsertCharacter.qml +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/InsertCharacter.qml @@ -111,6 +111,8 @@ Popup { model.append({ 'chr': chr }) } } + + Keys.onEscapePressed: parent.close() } function setFocus() { @@ -118,5 +120,4 @@ Popup { insertGrid.forceActiveFocus() } - Keys.onEscapePressed: close() }