├── .github
└── workflows
│ └── main.yml
├── .gitignore
├── Algebra.js
├── BREAKING_CHANGES.md
├── CITATION.cff
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Calculus.js
├── Extra.js
├── LICENSE
├── README.md
├── Solve.js
├── all.js
├── all.min.js
├── gulpfile.js
├── index.d.ts
├── index.html
├── license.txt
├── nerdamer.core.js
├── package-lock.json
├── package.json
└── spec
├── LaTeX.spec.js
├── TeXConvert.spec.js
├── algebra.spec.js
├── basic_parser.spec.js
├── build.spec.js
├── calculus.spec.js
├── core.spec.js
├── extra.spec.js
├── solve.spec.js
├── support
├── jasmine.json
└── utils.js
├── test.js
├── text.spec.js
└── unsolved.spec.js
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: nerdamer prime CI
2 |
3 | on:
4 | push:
5 | branches: ["main"]
6 | pull_request:
7 | branches: ["main"]
8 | workflow_dispatch:
9 |
10 | jobs:
11 | build-and-test:
12 | runs-on: [ubuntu-latest]
13 | name: Build and test
14 | steps:
15 | - uses: actions/checkout@v4
16 |
17 | - name: Use Node.js 20
18 | uses: actions/setup-node@v4
19 | with:
20 | node-version: 20
21 | cache: 'npm'
22 | cache-dependency-path: |
23 | ./package-lock.json
24 |
25 | - name: Install dependencies
26 | shell: bash
27 | run: npm ci
28 |
29 | # results in an error, so commenting out
30 |
31 | #- name: Audit fix
32 | # shell: bash
33 | # run: npm audit fix --audit-level=high
34 |
35 | - name: Run tests
36 | shell: bash
37 | run: npm run test
38 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /nbproject/private/
2 | /nbproject/
3 | /Algebra.temp.js
4 | /.idea/
5 | /node_modules/
6 | /package-lock.json
7 | /.vscode
--------------------------------------------------------------------------------
/BREAKING_CHANGES.md:
--------------------------------------------------------------------------------
1 | *version 0.8.0*
2 | - Order of arguments changed for *defint* in `nerdamer.convertToLaTeX`
3 | - `nerdamer.toRPN` not returns array of tokens. Token class can be found in Parser.classes
4 | - The `Operator` class was removed. A simple `object` can be passed to method
5 | - nerdamer.setOperator and nerdamer.getOperators have been deprecated. Use `core.PARSER.setOperator` instead.
6 |
--------------------------------------------------------------------------------
/CITATION.cff:
--------------------------------------------------------------------------------
1 | cff-version: 1.2.0
2 | title: "Nerdamer"
3 | message: "If you use this software, please cite it as below.""
4 | version: 1.1.3
5 | date-released: 2021-11-01
6 | url: "https://nerdamer.com"
7 | license: "MIT License"
8 | authors:
9 | - family-names: Donk
10 | given-names: Martin
11 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | * Using welcoming and inclusive language
12 | * Being respectful of differing viewpoints and experiences
13 | * Gracefully accepting constructive criticism
14 | * Focusing on what is best for the community
15 | * Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances
20 | * Trolling, insulting/derogatory comments, and personal or political attacks
21 | * Public or private harassment
22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission
23 | * Other conduct which could reasonably be considered inappropriate in a professional setting
24 |
25 | ## Our Responsibilities
26 |
27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 |
29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | ## Scope
32 |
33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34 |
35 | ## Enforcement
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at martin.r.donk@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 |
39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
44 |
45 | [homepage]: http://contributor-covenant.org
46 | [version]: http://contributor-covenant.org/version/1/4/
47 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 | Thank you for your interest in contributing to the project. You can submit a pull request at any time with your changes. Please make sure to target the `dev` branch when submitting and not the `master` branch.
3 |
4 | Any contribution is welcomed granted the following are performed:
5 |
6 | 1. Make sure that all unit tests pass. The unit tests are written for **jasmine**. To run the tests, first make sure you install [node.js](https://nodejs.org/en/download/) and [jasmine](https://www.npmjs.com/package/jasmine). Follow the link to install nodejs. You can install jasmine by running `npm install -g jasmine`. Once that's complete you can navigate to your project folder root and type `jasmine`. Please be aware that at the moment, some tests will fail for versions of node.js > 10.xx due to rounding errors.
7 | 2. Make sure that there's isn't already an existing solution on the `dev` branch.
8 |
9 | Feel free to reach out if you need any guidance navigating the code. Seriously. A slow response doesn't mean you're being ignored but simply that life is in progress. You will however get a response at some point.
10 |
11 | # Issues
12 | Please do report any issues you find. If you don't report it, then there's no way of knowing the issue exists in the first place. There are existing issues labeled "Pending issues with xxx". These are just to keep related issues organized. So for instance if you're having a problem with `simplify` you can just add your issue under "Pending issues with simplify". Be sure to mention @jiggzson when reporting.
13 |
14 | Give as much information as possible about your issue and try to give it a meaningful subject line. If possible, add an example of what works and what doesn't. If you've already done some debugging, feel free to share since it may help speed up the fix.
--------------------------------------------------------------------------------
/Extra.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Author : Martin Donk
3 | * Website : http://www.nerdamer.com
4 | * Email : martin.r.donk@gmail.com
5 | * License : MIT
6 | * Source : https://github.com/jiggzson/nerdamer
7 | */
8 |
9 | /* global module */
10 |
11 | if((typeof module) !== 'undefined') {
12 | var nerdamer = require('./nerdamer.core.js');
13 | require('./Calculus');
14 | require('./Algebra');
15 | }
16 |
17 | (function () {
18 | "use strict";
19 |
20 | var core = nerdamer.getCore(),
21 | _ = core.PARSER,
22 | Symbol = core.Symbol,
23 | format = core.Utils.format,
24 | isVector = core.Utils.isVector,
25 | isArray = core.Utils.isArray,
26 | Vector = core.Vector,
27 | S = core.groups.S,
28 | EX = core.groups.EX,
29 | CP = core.groups.CP,
30 | PL = core.groups.PL,
31 | CB = core.groups.CB,
32 | FN = core.groups.FN;
33 | core.Settings.Laplace_integration_depth = 40;
34 |
35 |
36 | Symbol.prototype.findFunction = function (fname) {
37 | //this is what we're looking for
38 | if(this.group === FN && this.fname === fname)
39 | return this.clone();
40 | var found;
41 | if(this.symbols)
42 | for(var x in this.symbols) {
43 | found = this.symbols[x].findFunction(fname);
44 | if(found)
45 | break;
46 | }
47 |
48 | return found;
49 | };
50 |
51 | var __ = core.Extra = {
52 | version: '1.4.2',
53 | //http://integral-table.com/downloads/LaplaceTable.pdf
54 | //Laplace assumes all coefficients to be positive
55 | LaPlace: {
56 | //Using: integral_0^oo f(t)*e^(-s*t) dt
57 | transform: function (symbol, t, s) {
58 | symbol = symbol.clone();
59 |
60 | t = t.toString();
61 | //First try a lookup for a speed boost
62 | symbol = Symbol.unwrapSQRT(symbol, true);
63 | var retval,
64 | coeff = symbol.stripVar(t),
65 | g = symbol.group;
66 |
67 | symbol = _.divide(symbol, coeff.clone());
68 |
69 | if(symbol.isConstant() || !symbol.contains(t, true)) {
70 | retval = _.parse(format('({0})/({1})', symbol, s));
71 | }
72 | else if(g === S && core.Utils.isInt(symbol.power)) {
73 | var n = String(symbol.power);
74 | retval = _.parse(format('factorial({0})/({1})^({0}+1)', n, s));
75 | }
76 | else if(symbol.group === S && symbol.power.equals(1 / 2)) {
77 | retval = _.parse(format('sqrt(pi)/(2*({0})^(3/2))', s));
78 | }
79 | else if(symbol.isComposite()) {
80 | retval = new Symbol(0);
81 | symbol.each(function (x) {
82 | retval = _.add(retval, __.LaPlace.transform(x, t, s));
83 | }, true);
84 | }
85 | else if(symbol.isE() && (symbol.power.group === S || symbol.power.group === CB)) {
86 | var a = symbol.power.stripVar(t);
87 | retval = _.parse(format('1/(({1})-({0}))', a, s));
88 | }
89 | else {
90 | var fns = ['sin', 'cos', 'sinh', 'cosh'];
91 | //support for symbols in fns with arguments in the form a*t or n*t where a = symbolic and n = Number
92 | if(symbol.group === FN && fns.indexOf(symbol.fname) !== -1 && (symbol.args[0].group === S || symbol.args[0].group === CB)) {
93 | var a = symbol.args[0].stripVar(t);
94 |
95 | switch(symbol.fname) {
96 | case 'sin':
97 | retval = _.parse(format('({0})/(({1})^2+({0})^2)', a, s));
98 | break;
99 | case 'cos':
100 | retval = _.parse(format('({1})/(({1})^2+({0})^2)', a, s));
101 | break;
102 | case 'sinh':
103 | retval = _.parse(format('({0})/(({1})^2-({0})^2)', a, s));
104 | break;
105 | case 'cosh':
106 | retval = _.parse(format('({1})/(({1})^2-({0})^2)', a, s));
107 | break;
108 | }
109 |
110 | }
111 | else {
112 | //Try to integrate for a solution
113 | //we need at least the Laplace integration depth
114 | var depth_is_lower = core.Settings.integration_depth < core.Settings.Laplace_integration_depth;
115 |
116 | if(depth_is_lower) {
117 | var integration_depth = core.Settings.integration_depth; //save the depth
118 | core.Settings.integration_depth = core.Settings.Laplace_integration_depth; //transforms need a little more room
119 | }
120 |
121 | core.Utils.block('PARSE2NUMBER', function () {
122 | var u = t;
123 | var sym = symbol.sub(t, u);
124 | var integration_expr = _.parse('e^(-' + s + '*' + u + ')*' + sym);
125 | retval = core.Calculus.integrate(integration_expr, u);
126 | if(retval.hasIntegral())
127 | return _.symfunction('laplace', arguments);
128 | // _.error('Unable to compute transform');
129 | retval = retval.sub(t, 0);
130 | retval = _.expand(_.multiply(retval, new Symbol(-1)));
131 | retval = retval.sub(u, t);
132 | }, false);
133 |
134 | retval = core.Utils.block('PARSE2NUMBER', function () {
135 | return _.parse(retval);
136 | }, true);
137 |
138 | if(depth_is_lower)//put the integration depth as it was
139 | core.Settings.integration_depth = integration_depth;
140 | }
141 |
142 | }
143 |
144 | return _.multiply(retval, coeff);
145 | },
146 | inverse: function (symbol, s_, t) {
147 | var input_symbol = symbol.clone();
148 | return core.Utils.block('POSITIVE_MULTIPLIERS', function () {
149 | //expand and get partial fractions
150 | if(symbol.group === CB) {
151 | symbol = core.Algebra.PartFrac.partfrac(_.expand(symbol), s_);
152 | }
153 |
154 | if(symbol.group === S || symbol.group === CB || symbol.isComposite()) {
155 | var finalize = function () {
156 | //put back the numerator
157 | retval = _.multiply(retval, num);
158 | retval.multiplier = retval.multiplier.multiply(symbol.multiplier);
159 | //put back a
160 | retval = _.divide(retval, f.a);
161 | };
162 | var num, den, s, retval, f, p, m, den_p, fe;
163 | //remove the multiplier
164 | m = symbol.multiplier.clone();
165 | symbol.toUnitMultiplier();
166 | //get the numerator and denominator
167 | num = symbol.getNum();
168 | den = symbol.getDenom().toUnitMultiplier();
169 |
170 | //TODO: Make it so factor doesn't destroy pi
171 | //num = core.Algebra.Factor.factor(symbol.getNum());
172 | //den = core.Algebra.Factor.factor(symbol.getDenom().invert(null, true));
173 |
174 | if(den.group === CP || den.group === PL) {
175 | den_p = den.power.clone();
176 | den.toLinear();
177 | }
178 | else {
179 | den_p = new core.Frac(1);
180 | }
181 |
182 | //convert s to a string
183 | s = s_.toString();
184 | //split up the denominator if in the form ax+b
185 | f = core.Utils.decompose_fn(den, s, true);
186 | //move the multiplier to the numerator
187 | fe = core.Utils.decompose_fn(_.expand(num.clone()), s, true);
188 | num.multiplier = num.multiplier.multiply(m);
189 | //store the parts in variables for easy recognition
190 | //check if in the form t^n where n = integer
191 | if((den.group === S || den.group === CB) && f.x.value === s && f.b.equals(0) && core.Utils.isInt(f.x.power)) {
192 | var fact, p;
193 | p = f.x.power - 1;
194 | fact = core.Math2.factorial(p);
195 | // n!/s^(n-1)
196 | retval = _.divide(_.pow(t, new Symbol(p)), new Symbol(fact));
197 | //wrap it up
198 | finalize();
199 | }
200 | else if(den.group === CP && den_p.equals(1)) {
201 | if(f.x.group === core.groups.PL && core.Algebra.degree(den).equals(2)) {
202 | // Possibly in the form 1/(s^2+2*s+1)
203 | // Try factoring to get it in a more familiar form{
204 | // Apply inverse of F(s-a)
205 | var completed = core.Algebra.sqComplete(den, s);
206 | var u = core.Utils.getU(den);
207 | // Get a for the function above
208 | var a = core.Utils.decompose_fn(completed.a, s, true).b;
209 | var tf = __.LaPlace.inverse(_.parse(`1/((${u})^2+(${completed.c}))`), u, t);
210 | retval = _.multiply(tf, _.parse(`(${m})*e^(-(${a})*(${t}))`));
211 | }
212 | else {
213 | // a/(b*s-c) -> ae^(-bt)
214 | if(f.x.isLinear() && !num.contains(s)) {
215 | t = _.divide(t, f.a.clone());
216 |
217 | // Don't add factorial of one or zero
218 | var p = den_p - 1;
219 | var fact = p === 0 || p === 1 ? '1' : `(${den_p}-1)!`
220 | retval = _.parse(format('(({0})^({3}-1)*e^(-(({2})*({0}))/({1})))/(({4})*({1})^({3}))', t, f.a, f.b, den_p, fact));
221 | //wrap it up
222 | finalize();
223 | }
224 | else {
225 | if(f.x.group === S && f.x.power.equals(2)) {
226 | if(!num.contains(s)) {
227 | retval = _.parse(format('(({1})*sin((sqrt(({2})*({3}))*({0}))/({2})))/sqrt(({2})*({3}))', t, num, f.a, f.b));
228 | }
229 | // a*s/(b*s^2+c^2)
230 | else {
231 | var a = new Symbol(1);
232 | if(num.group === CB) {
233 | var new_num = new Symbol(1);
234 | num.each(function (x) {
235 | if(x.contains(s))
236 | new_num = _.multiply(new_num, x);
237 | else
238 | a = _.multiply(a, x);
239 | });
240 | num = new_num;
241 | }
242 |
243 | //we need more information about the denominator to decide
244 | var f2 = core.Utils.decompose_fn(num, s, true);
245 | var fn1, fn2, a_has_sin, b_has_cos, a_has_cos, b_has_sin;
246 | fn1 = f2.a;
247 | fn2 = f2.b;
248 | a_has_sin = fn1.containsFunction('sin');
249 | a_has_cos = fn1.containsFunction('cos');
250 | b_has_cos = fn2.containsFunction('cos');
251 | b_has_sin = fn2.containsFunction('sin');
252 | if(f2.x.value === s && f2.x.isLinear() && !((a_has_sin && b_has_cos) || (a_has_cos || b_has_sin))) {
253 | retval = _.parse(format('(({1})*cos((sqrt(({2})*({3}))*({0}))/({2})))/({2})', t, f2.a, f.a, f.b));
254 | }
255 | else {
256 | if(a_has_sin && b_has_cos) {
257 | var sin, cos;
258 | sin = fn1.findFunction('sin');
259 | cos = fn2.findFunction('cos');
260 | //who has the s?
261 | if(sin.args[0].equals(cos.args[0]) && !sin.args[0].contains(s)) {
262 | var b, c, d, e;
263 | b = _.divide(fn2, cos.toUnitMultiplier()).toString();
264 | c = sin.args[0].toString();
265 | d = f.b;
266 | e = _.divide(fn1, sin.toUnitMultiplier());
267 | exp = '(({1})*({2})*cos({3})*sin(sqrt({4})*({0})))/sqrt({4})+({1})*sin({3})*({5})*cos(sqrt({4})*({0}))';
268 | retval = _.parse(format(exp, t, a, b, c, d, e));
269 | }
270 | }
271 | }
272 | }
273 | }
274 | }
275 | }
276 | }
277 | else if(f.x.power.num && f.x.power.num.equals(3) && f.x.power.den.equals(2) && num.contains('sqrt(pi)') && !num.contains(s) && num.isLinear()) {
278 | var b = _.divide(num.clone(), _.parse('sqrt(pi)'));
279 | retval = _.parse(format('(2*({2})*sqrt({0}))/({1})', t, f.a, b, num));
280 | }
281 | else if(den_p.equals(2) && f.x.power.equals(2)) {
282 | var a, d, exp;
283 | if(!num.contains(s)) {
284 | a = _.divide(num, new Symbol(2));
285 | exp = '(({1})*sin((sqrt(({2})*({3}))*({0}))/({2})))/(({3})*sqrt(({2})*({3})))-(({1})*({0})*cos((sqrt(({2})*({3}))*({0}))/({2})))/(({2})*({3}))';
286 | retval = _.parse(format(exp, t, a, f.a, f.b));
287 | }
288 | else {
289 | // Decompose the numerator to check value of s
290 | f2 = core.Utils.decompose_fn(_.expand(num.clone()), s, true);
291 | if(f2.x.isComposite()) {
292 | var s_terms = [];
293 | //first collect the factors e.g. (a)(bx)(cx^2+d)
294 | var symbols = num.collectSymbols(function (x) {
295 | x = Symbol.unwrapPARENS(x);
296 | var t = core.Utils.decompose_fn(x, s, true);
297 | t.symbol = x;
298 | return t;
299 | }).
300 | //then sort them by power hightest to lowest
301 | sort(function (a, b) {
302 | var p1, p2;
303 | p1 = a.x.value !== s ? 0 : a.x.power;
304 | p2 = b.x.value !== s ? 0 : b.x.power;
305 | return p2 - p1;
306 | });
307 | a = new Symbol(-1);
308 | // Grab only the ones which have s
309 | for(var i = 0; i < symbols.length; i++) {
310 | var fc = symbols[i];
311 | if(fc.x.value === s)
312 | s_terms.push(fc);
313 | else
314 | a = _.multiply(a, fc.symbol);
315 | }
316 | // The following 2 assumptions are made
317 | // 1. since the numerator was factored above then each s_term has a unique power
318 | // 2. because the terms are sorted by descending powers then the first item
319 | // has the highest power
320 | // We can now check for the next type s(s^2-a^2)/(s^2+a^2)^2
321 | if(s_terms[0].x.power.equals(2) && s_terms[1].x.power.equals(1) && s_terms[1].b.equals(0) && !s_terms[0].b.equals(0)) {
322 | b = s_terms[0].a.negate();
323 | exp = '-(({1})*({2})*({5})*({0})*sin((sqrt(({4})*({5}))*({0}))/({4})))/' +
324 | '(2*({4})^2*sqrt(({4})*({5})))-(({1})*({3})*({0})*sin((sqrt(({4})*({5}))*({0}))/({4})))' +
325 | '/(2*({4})*sqrt(({4})*({5})))+(({1})*({2})*cos((sqrt(({4})*({5}))*({0}))/({4})))/({4})^2';
326 | retval = _.parse(format(exp, t, a, b, s_terms[0].b, f.a, f.b));
327 | }
328 | }
329 | else {
330 | if(f2.x.isLinear()) {
331 | a = _.divide(f2.a, new Symbol(2));
332 | exp = '(({1})*({0})*sin((sqrt(({2})*({3}))*({0}))/({2})))/(({2})*sqrt(({2})*({3})))';
333 | retval = _.parse(format(exp, t, a, f.a, f.b));
334 | }
335 | else if(f2.x.power.equals(2)) {
336 | if(f2.b.equals(0)) {
337 | a = _.divide(f2.a, new Symbol(2));
338 | exp = '(({1})*sin((sqrt(({2})*({3}))*({0}))/({2})))/(({2})*sqrt(({2})*({3})))+(({1})*({0})*cos((sqrt(({2})*({3}))*({0}))/({2})))/({2})^2';
339 | retval = _.parse(format(exp, t, a, f.a, f.b));
340 | }
341 | else {
342 | a = _.divide(f2.a, new Symbol(2));
343 | d = f2.b.negate();
344 | exp = '-((({2})*({4})-2*({1})*({3}))*sin((sqrt(({2})*({3}))*({0}))/({2})))/(2*({2})*({3})*sqrt(({2})*({3})))+' +
345 | '(({4})*({0})*cos((sqrt(({2})*({3}))*({0}))/({2})))/(2*({2})*({3}))+(({1})*({0})*cos((sqrt(({2})*({3}))*({0}))/({2})))/({2})^2';
346 | retval = _.parse(format(exp, t, a, f.a, f.b, d));
347 |
348 | }
349 | }
350 | }
351 | }
352 | }
353 | else if(symbol.isComposite()) {
354 | // 1/(s+1)^2
355 | if(den_p.equals(2) && f.x.group === S) {
356 | retval = _.parse(`(${m})*(${t})*e^(-(${f.b})*(${t}))`);
357 | }
358 | else {
359 | retval = new Symbol(0);
360 |
361 | symbol = core.Algebra.PartFrac.partfrac(_.expand(symbol), s_);
362 |
363 | symbol.each(function (x) {
364 | retval = _.add(retval, __.LaPlace.inverse(x, s_, t));
365 | }, true);
366 | }
367 | }
368 | }
369 |
370 | if(!retval) {
371 | retval = _.symfunction('ilt', [input_symbol, s_, t]);
372 | }
373 |
374 | return retval;
375 | }, true);
376 | }
377 | },
378 | Statistics: {
379 | frequencyMap: function (arr) {
380 | var map = {};
381 | //get the frequency map
382 | for(var i = 0, l = arr.length; i < l; i++) {
383 | var e = arr[i],
384 | key = e.toString();
385 | if(!map[key]) //default it to zero
386 | map[key] = 0;
387 | map[key]++; //increment
388 | }
389 | return map;
390 | },
391 | sort: function (arr) {
392 | return arr.sort(function (a, b) {
393 | if(!a.isConstant() || !b.isConstant())
394 | _.error('Unable to sort! All values must be numeric');
395 | return a.multiplier.subtract(b.multiplier);
396 | });
397 | },
398 | count: function (arr) {
399 | return new Symbol(arr.length);
400 | },
401 | sum: function (arr, x_) {
402 | var sum = new Symbol(0);
403 | for(var i = 0, l = arr.length; i < l; i++) {
404 | var xi = arr[i].clone();
405 | if(x_) {
406 | sum = _.add(_.pow(_.subtract(xi, x_.clone()), new Symbol(2)), sum);
407 | }
408 | else
409 | sum = _.add(xi, sum);
410 | }
411 |
412 | return sum;
413 | },
414 | mean: function () {
415 | var args = [].slice.call(arguments);
416 | //handle arrays
417 | if(isVector(args[0]))
418 | return __.Statistics.mean.apply(this, args[0].elements);
419 | return _.divide(__.Statistics.sum(args), __.Statistics.count(args));
420 | },
421 | median: function () {
422 | var args = [].slice.call(arguments), retval;
423 | //handle arrays
424 | if(isVector(args[0]))
425 | return __.Statistics.median.apply(this, args[0].elements);
426 | try {
427 | var sorted = __.Statistics.sort(args);
428 | var l = args.length;
429 | if(core.Utils.even(l)) {
430 | var mid = l / 2;
431 | retval = __.Statistics.mean(sorted[mid - 1], sorted[mid]);
432 | }
433 | else
434 | retval = sorted[Math.floor(l / 2)];
435 | }
436 | catch(e) {
437 | if (e.message === "timeout") throw e;
438 | retval = _.symfunction('median', args);
439 | }
440 | return retval;
441 | },
442 | mode: function () {
443 | var args = [].slice.call(arguments),
444 | retval;
445 | //handle arrays
446 | if(isVector(args[0]))
447 | return __.Statistics.mode.apply(this, args[0].elements);
448 |
449 | var map = __.Statistics.frequencyMap(args);
450 |
451 | //the mode of 1 item is that item as per issue #310 (verified by Happypig375).
452 | if(core.Utils.keys(map).length === 1)
453 | retval = args[0];
454 | else {
455 | //invert by arraning them according to their frequency
456 | var inverse = {};
457 | for(var x in map) {
458 | var freq = map[x];
459 | //check if it's in the inverse already
460 | if(!(freq in inverse))
461 | inverse[freq] = x;
462 | else {
463 | var e = inverse[freq];
464 | //if it's already an array then just add it
465 | if(isArray(e))
466 | e.push(x);
467 | //convert it to and array
468 | else
469 | inverse[freq] = [x, inverse[freq]];
470 | }
471 | }
472 | //the keys now represent the maxes. We want the max of those keys
473 | var max = inverse[Math.max.apply(null, core.Utils.keys(inverse))];
474 | //check it's an array. If it is then map over the results and convert
475 | //them to Symbol
476 | if(isArray(max)) {
477 | retval = _.symfunction('mode', max.sort());
478 | }
479 | else
480 | retval = _.parse(max);
481 | }
482 |
483 | return retval;
484 | },
485 | gVariance: function (k, args) {
486 | var x_ = __.Statistics.mean.apply(__.Statistics, args),
487 | sum = __.Statistics.sum(args, x_);
488 | return _.multiply(k, sum);
489 | },
490 | variance: function () {
491 | var args = [].slice.call(arguments);
492 | //handle arrays
493 | if(isVector(args[0]))
494 | return __.Statistics.variance.apply(this, args[0].elements);
495 | var k = _.divide(new Symbol(1), __.Statistics.count(args));
496 | return __.Statistics.gVariance(k, args);
497 | },
498 | sampleVariance: function () {
499 | var args = [].slice.call(arguments);
500 | //handle arrays
501 | if(isVector(args[0]))
502 | return __.Statistics.sampleVariance.apply(this, args[0].elements);
503 |
504 | var k = _.divide(new Symbol(1), _.subtract(__.Statistics.count(args), new Symbol(1)));
505 | return __.Statistics.gVariance(k, args);
506 | },
507 | standardDeviation: function () {
508 | var args = [].slice.call(arguments);
509 | //handle arrays
510 | if(isVector(args[0]))
511 | return __.Statistics.standardDeviation.apply(this, args[0].elements);
512 | return _.pow(__.Statistics.variance.apply(__.Statistics, args), new Symbol(1 / 2));
513 | },
514 | sampleStandardDeviation: function () {
515 | var args = [].slice.call(arguments);
516 | //handle arrays
517 | if(isVector(args[0]))
518 | return __.Statistics.sampleStandardDeviation.apply(this, args[0].elements);
519 | return _.pow(__.Statistics.sampleVariance.apply(__.Statistics, args), new Symbol(1 / 2));
520 | },
521 | zScore: function (x, mean, stdev) {
522 | return _.divide(_.subtract(x, mean), stdev);
523 | }
524 | },
525 | Units: {
526 | table: {
527 | foot: '12 inch',
528 | meter: '100 cm',
529 | decimeter: '10 cm',
530 |
531 | }
532 | }
533 | };
534 |
535 | nerdamer.register([
536 | {
537 | name: 'laplace',
538 | visible: true,
539 | numargs: 3,
540 | build: function () {
541 | return __.LaPlace.transform;
542 | }
543 | },
544 | {
545 | name: 'ilt',
546 | visible: true,
547 | numargs: 3,
548 | build: function () {
549 | return __.LaPlace.inverse;
550 | }
551 | },
552 | //statistical
553 | {
554 | name: 'mean',
555 | visible: true,
556 | numargs: -1,
557 | build: function () {
558 | return __.Statistics.mean;
559 | }
560 | },
561 | {
562 | name: 'median',
563 | visible: true,
564 | numargs: -1,
565 | build: function () {
566 | return __.Statistics.median;
567 | }
568 | },
569 | {
570 | name: 'mode',
571 | visible: true,
572 | numargs: -1,
573 | build: function () {
574 | return __.Statistics.mode;
575 | }
576 | },
577 | {
578 | name: 'smpvar',
579 | visible: true,
580 | numargs: -1,
581 | build: function () {
582 | return __.Statistics.sampleVariance;
583 | }
584 | },
585 | {
586 | name: 'variance',
587 | visible: true,
588 | numargs: -1,
589 | build: function () {
590 | return __.Statistics.variance;
591 | }
592 | },
593 | {
594 | name: 'smpstdev',
595 | visible: true,
596 | numargs: -1,
597 | build: function () {
598 | return __.Statistics.sampleStandardDeviation;
599 | }
600 | },
601 | {
602 | name: 'stdev',
603 | visible: true,
604 | numargs: -1,
605 | build: function () {
606 | return __.Statistics.standardDeviation;
607 | }
608 | },
609 | {
610 | name: 'zscore',
611 | visible: true,
612 | numargs: 3,
613 | build: function () {
614 | return __.Statistics.zScore;
615 | }
616 | }
617 | ]);
618 |
619 | //link registered functions externally
620 | nerdamer.updateAPI();
621 | }());
622 |
623 | // Added for all.min.js
624 | if((typeof module) !== 'undefined') {
625 | module.exports = nerdamer;
626 | };
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 together-science
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Nerdamer-prime
2 | ==============
3 |
4 | This is a continuation of Martin Donk's (jiggzson) [Nerdamer project](https://nerdamer.com/). We forked this a while ago,
5 | but are up to speed with everything Martin did before he archived the original repo.
6 |
7 | The license is unchanged, everything is free under MIT terms.
8 |
9 | The npm js installation point has moved: `npm i nerdamer-prime`.
10 |
11 | NOTICE: Starting with version 1.2.0, vector and matrix semantics are changing to be more useful for linear algebra. If this causes you problems, please stick with 1.1.26 and let us know so we can see how we could make compatible changes.
12 |
13 | Our intentions:
14 |
15 | To keep Nerdamer in good repair, and make improvements where we need them and where we can. Mostly bug fixes. If you investigate
16 | some of the bugs in the old repo, you will see that people ask for "things it should be able to do", mostly to do with simplifications.
17 | This kind of stuff is difficult, and it is more difficult in someone else's codebase. Nerdamer wasn't meant to be a complete symbolic
18 | algebra system. Its wealth of features can fool you, though. Its features and the fact that it works fast, in the browser and NodeJS,
19 | makes it still worthwhile. But please understand that it will not achieve a whole lot more than it can do right now. Consider using
20 | e.g. SymPy in a WASM webworker if you need more. See here for a demo: [SymPy live](https://live.sympy.org/)
21 |
22 | We have made some improvements - simplification of logs and squareroots, and bug fixes related to those areas and factoring. Unit tests
23 | are fixed except for a couple of known flaws. There will be further work in this area. We will also work on vectors, which are not
24 | useful for our product [together.math](https://www.together.science/) today. Those will be breaking changes
25 | (*THESE ARE NOW IN PROGRESS* for versions >= 1.2.0, you are welcome to fork an earlier version).
26 |
27 | If you have a clear bug, file an issue with the code to repro. If you want a new feature - and that includes many things that you will
28 | think of as "obvious flaws" - file the issue, but we probably won't do it. But someone else can! We will absolutely consider compatible
29 | PRs, but best to [talk to us](mailto:info@together.science) before you start.
30 |
31 | Below follows the original README.
32 |
33 |
34 | Nerdamer
35 | ========
36 |
37 | As of version 0.5.0, the library is split into the core and optional add-ons which can be loaded after the core has been loaded.
38 |
39 | Getting started with Nerdamer
40 |
41 | Load the library in your html page
42 |
43 | ```html
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | ```
54 | Or import everything
55 | ```html
56 |
57 | ```
58 | If you're using node.js install it using `npm i nerdamer-prime` (GM: originally `npm i nerdamer`)and then
59 |
60 | ```javascript
61 | // const cannot be used since nerdamer gets modified when other modules are loaded
62 | var nerdamer = require('nerdamer');
63 | // Load additional modules. These are not required.
64 | require('nerdamer/Algebra');
65 | require('nerdamer/Calculus');
66 | require('nerdamer/Solve');
67 | require('nerdamer/Extra');
68 | ```
69 | Or do a single import to import everything
70 | ```javascript
71 | const nerdamer = require("nerdamer/all.min")
72 | ```
73 |
74 | Some functions have dependencies from other add-ons.
75 |
76 | You can see nerdamer in action at http://nerdamer.com/demo
77 |
78 | For full documentation go to http://nerdamer.com/documentation
79 |
80 | All operations are done using the 'nerdamer' object.
81 |
82 | To add an expression just add it to the nerdamer object which will return a `Expression` object.
83 |
84 | ```javascript
85 | var e = nerdamer('x^2+2*(cos(x)+x*x)');
86 | console.log(e.text());
87 |
88 | //result:
89 | //2*cos(x)+3*x^2
90 | ```
91 | It is also possible to use `nerdamer` functions directly within the need for string manipulation of the input. The input will be parsed and the output will of type `Expression`. For example:
92 | ```javascript
93 | var ans = nerdamer.expand('(x-1)^5');
94 | console.log(ans.text());
95 | // -1-10*x^2-5*x^4+10*x^3+5*x+x^5
96 |
97 | var sol = nerdamer.solve('x^2-4', 'x');
98 | console.log(sol.text())
99 | // [2,-2]
100 | ```
101 |
102 | You can also pass in an object with known values as the second parameter.
103 |
104 | ```javascript
105 | var e = nerdamer('x^2+2*(cos(x)+x*x)',{x:6});
106 | console.log(e.text());
107 |
108 | //result:
109 | //108+2*cos(6)
110 | ```
111 |
112 |
113 | As you can see only the substitution is performed. To evaluate the result just call evaluate.
114 | Note that evaluate returns a text string or a number not an object.
115 |
116 | ```javascript
117 | var e = nerdamer('x^2+2*(cos(x)+x*x)',{x:6}).evaluate();
118 | console.log(e.text());
119 |
120 | //result:
121 | //109.9203405733006
122 | ```
123 | To get back the text as a fraction, call the text method and pass in the string 'fractions'.
124 |
125 | ```javascript
126 | var e = nerdamer('x^2+2*(cos(x)+x*x)',{x:6}).evaluate();
127 | console.log(e.text('fractions'));
128 |
129 | //result:
130 | //429607273/3908351
131 | ```
132 | You can get your expression back as LaTeX by calling the toTeX method
133 | ```javascript
134 | var LaTeX = nerdamer('x^2+2*(cos(x)+x*x)',{x:0.25}).toTeX();
135 | console.log(LaTeX);
136 |
137 | //result:
138 | //2 \cdot \mathrm{cos}\left(\frac{1}{4}\right)+\frac{3}{16}
139 | ```
140 |
141 | To have numbers returned as decimals pass in the string 'decimals' to the toTeX method
142 |
143 | ```javascript
144 | var LaTeX = nerdamer('x^2+2*(cos(x)+x*x)',{x:0.25}).toTeX('decimal');
145 | console.log(LaTeX);
146 |
147 | //result:
148 | //2 \cdot \mathrm{cos}\left(0.25\right)+0.1875
149 | ```
150 |
151 | Alternatively you can pass an object containing known values into evaluate method instead. The values passed in don't have to be number they can be another expression if needed.
152 |
153 | ```javascript
154 | var e = nerdamer('x^2+2*(cos(x)+x*x)',{x:'x^2+1'});
155 | console.log(e.text());
156 |
157 | //result:
158 | //2*cos(1+x^2)+3*(1+x^2)^2
159 | ```
160 |
161 | Every time you parse an expression it's stored in nerdamer. To get a list of all the expressions you just call
162 | nerdamer.expressions().
163 |
164 | ```javascript
165 | var knownValues = {x:'x^2+1'};
166 | nerdamer('x^2+2*(cos(x)+x*x)').evaluate(knownValues);
167 | nerdamer('sin(x)^2+cos(x)^2').evaluate(knownValues);
168 |
169 | console.log(nerdamer.expressions());
170 |
171 | //result:
172 | //[ 46.692712758272776, 1 ]
173 | ```
174 |
175 | You can request it as an object as well by passing in true. This can be convenient in some
176 | situations as the numbering starts at 1;
177 |
178 | ```javascript
179 | var knownValues = {x:'x^2+1'};
180 | nerdamer('x^2+2*(cos(x)+x*x)', knownValues );
181 | nerdamer('sin(x)^2+cos(x)^2', knownValues );
182 |
183 | console.log(nerdamer.expressions(true));
184 |
185 | //{ '1': '2*cos(1+x^(2))+3*(1+x^(2))^(2)',
186 | //'2': 'cos(1+x^(2))^(2)+sin(1+x^(2))^(2)' }
187 | ```
188 |
189 | Functions aren't always immediately parsed to numbers. For example
190 |
191 | ```javascript
192 | var result = nerdamer('cos(x)',{x:6});
193 | console.log(result.text());
194 | //cos(6)
195 | ```
196 | will only subsitute out the variable name. To change this behaviour numer should be passed in as the 3rd argument.
197 |
198 | ```javascript
199 | var result = nerdamer('cos(x)',{x:6}, 'numer');
200 | console.log(result.text());
201 | //0.960170286650366
202 | ```
203 | or alternatively
204 |
205 | ```javascript
206 | var result = nerdamer('cos(x)').evaluate({x:6});
207 | console.log(result.text());
208 | //0.960170286650366
209 | ```
210 | The difference however is that the first option directly substitutes the variables while the second first evaluates
211 | the expression and then makes the substitutions. This library utilizes native javascript functions as much as possible. As a result it inherits whatever rounding errors they possess. One major change with version 0.6.0 however, is dealing with floating point issues.
212 |
213 | ```javascript
214 | var result = nerdamer('sqrt(x)*sqrt(x)-2', {x: 2});
215 | console.log(result.text());
216 | //0
217 | ```
218 | The above expample now returns zero whereas in previous version the result would be 4.440892098500626e-16. Same goes for 0.1+0.2.
219 |
220 | An expression can be replaced directly by passing in the index of which expression to override. For example
221 |
222 | ```javascript
223 | nerdamer('cos(x)',{x:6}, 'numer');
224 | nerdamer('sin(x)+y',{x:6}, null, 1);
225 | console.log(nerdamer.expressions());
226 | //[ 'sin(6)+y' ]
227 | ```
228 |
229 | If multiple modifier options need to be passed into nerdamer you can do so using an array. For example ...
230 |
231 | ```javascript
232 | var e = nerdamer('cos(x)+(y-x)^2', {x:7}, ['expand', 'numer']);
233 | console.log(e.text());
234 | //-14*y+y^2+49.7539022543433
235 | ```
236 |
237 | If you need the code as LaTeX you can pass in true as the second parameter when requesting the expressions.
238 |
239 | ```javascript
240 | nerdamer('x^2+2*(cos(x)+x*x)');
241 | nerdamer('sin(x)^0.25+cos(x)^0.5' );
242 | var asObject = true;
243 | var asLaTeX = true;
244 | console.log(nerdamer.expressions(asObject, asLaTeX));
245 |
246 | /*{ '1': '2 \\cdot \\mathrm{cos}\\left(x\\right)+3 \\cdot x^{2}',
247 | '2': '\\sqrt{\\mathrm{cos}\\left(x\\right)}+\\mathrm{sin}\\left(x\\right)^{\\frac{1}{4}}' }*/
248 | ```
249 |
250 |
251 | You can specify a particular location when adding an expression, which is specified with the third parameter.
252 |
253 | ```javascript
254 | nerdamer('x^2+2*(cos(x)+x*x)');
255 | nerdamer('sin(x)^0.25+cos(x)^0.5' );
256 | nerdamer('expr-override', undefined, 2 );
257 | var asObject = false;
258 | var asLaTeX = true;
259 | console.log(nerdamer.expressions(asObject, asLaTeX));
260 |
261 | /* [ '2 \\cdot \\mathrm{cos}\\left(x\\right)+3 \\cdot x^{2}',
262 | '\\sqrt{\\mathrm{cos}\\left(x\\right)}+\\mathrm{sin}\\left(x\\right)^{\\frac{1}{4}}',
263 | 'expr-override' ]
264 | */
265 | ```
266 |
267 | Here's an example of reserved variable and function names.
268 |
269 | ```javascript
270 | var reserved = nerdamer.reserved();
271 | console.log(reserved);
272 | //result:
273 | /* csc, sec, cot, erf, fact, mod, GCD, QGCD, LCM, pow, PI, E, cos, sin, tan, acos, asin, atan, sinh, cosh, tanh, asinh, acosh, atanh, exp, min, max, floor, ceil, round, vector, matrix, parens, sqrt, log, expand, abs, invert, transpose, dot */
274 |
275 | //or as an array
276 |
277 | var reserved = nerdamer.reserved(true);
278 | console.log(reserved);
279 | //result:
280 | /* [ 'csc', 'sec', 'cot', 'erf', 'fact', 'mod', 'GCD', 'QGCD', 'LCM', 'pow', 'PI', 'E', 'cos', 'sin', 'tan', 'acos', 'asin', 'atan', 'sinh', 'cosh', 'tanh', 'asinh', 'acosh', 'atanh', 'exp', 'min', 'max', 'floor', 'ceil', 'round', 'vector', 'matrix',
281 | 'parens', 'sqrt', 'log', 'expand', 'abs', 'invert', 'transpose', 'dot' ] */
282 | ```
283 |
284 | Most math functions are passed in as part of the expression. If you want to differentiate for instance you just use the function diff which is located in the Calculus add-on as of version 0.5.0
285 |
286 | ```javascript
287 | var e = nerdamer('diff(x^2+2*(cos(x)+x*x),x)');
288 |
289 | console.log(e.text());
290 |
291 | //result:
292 | //-2*sin(x)+6*x
293 | ```
294 |
295 |
296 | Nerdamer can also handle runtime functions. To do this use the method setFunction.
297 | The runtime functions do have symbolic capabilities and support for imaginary numbers.
298 | The setFunction method is used as follows:
299 | Mode 1a:
300 | nerdamer.setFunction( functionDefinition )
301 |
302 | ```javascript
303 | nerdamer.setFunction('interpolate(y0,x0,y1,x1,x)=y0+(y1-y0)*((x-x0)/(x1-x0))')
304 | var answer = nerdamer('interpolate(4,1,34,7,2)').evaluate();
305 |
306 | console.log(answer);
307 |
308 | //result: 9
309 | ```
310 |
311 | Mode 1b:
312 | nerdamer.setFunction( functionName, functionParameters , functionBody )
313 |
314 | ```javascript
315 | //generate some points
316 | var f = function(x) { return 5*x-1; }
317 | console.log(f(1)); //4
318 | console.log(f(2)); //9 - value to be found
319 | console.log(f(7)); //34
320 |
321 | nerdamer.setFunction('interpolate', ['y0','x0','y1','x1','x'], 'y0+(y1-y0)*((x-x0)/(x1-x0))')
322 |
323 | var answer = nerdamer('interpolate(4,1,34,7,2)').evaluate();
324 |
325 | console.log(answer);
326 |
327 | //result: 9
328 | ```
329 |
330 | Mode 2:
331 | Custom functions alternatively be set in following manner.
332 |
333 | ```javascript
334 | nerdamer('hyp(a, b) = sqrt(a^2 + b^2) ');
335 |
336 | var result = nerdamer('hyp(3, 4)').evaluate().text();
337 | console.log(result);
338 | //result: 5
339 | ```
340 |
341 | Mode 3:
342 | Custom JavaScript functions can also be set. For example
343 |
344 | ```javascript
345 | function hyp(a, b) {
346 | return Math.sqrt(a*a + b*b);
347 | }
348 |
349 | nerdamer.setFunction(hyp);
350 |
351 | var result = nerdamer('hyp(3, 4)').evaluate().text();
352 | console.log(result);
353 | //result: 5
354 | ```
355 | Also
356 | ```javascript
357 | function hyp(a, b) {
358 | return Math.sqrt(a*a + b*b);
359 | }
360 |
361 | nerdamer(hyp);
362 |
363 | var result = nerdamer('hyp(3, 4)').evaluate().text();
364 | console.log(result);
365 | //result: 5
366 | ```
367 |
368 | If you need to add a constant use the setConstant method
369 |
370 | ```javascript
371 | nerdamer.setConstant( 'g', 9.81);
372 | var weight = nerdamer('100*g').text();
373 | console.log(weight);
374 | //result:
375 | //981
376 | ```
377 |
378 | To delete just set it to delete
379 |
380 | ```javascript
381 | nerdamer.setConstant( 'g', 9.81);
382 | var weight = nerdamer('100*g').text();
383 | console.log(weight);
384 | //981
385 | nerdamer.setConstant( 'g', 'delete');
386 | var weight = nerdamer('100*g').text();
387 | console.log(weight);
388 | //100*g
389 | ```
390 |
391 | You also have the option of exporting your function to a javascript function which can be useful if you need some
392 | filtering from user input. Do keep in mind that the parameters are sorted alphabetically for more than one
393 | parameter. To use it add the expression to nerdamer and use the buildFunction method.
394 |
395 | ```javascript
396 | var f = nerdamer('x^2+5').buildFunction();
397 | console.log(f(9));
398 |
399 | //result:
400 | //86
401 | ```
402 | If you have a particular order in which you need the parameters to be set, then you pass in an array with the variables in the order in which you want them for instance:
403 |
404 | ```javascript
405 | var f = nerdamer('z+x^2+y').buildFunction(['y', 'x', 'z']);
406 | console.log(f(9,2,1));
407 | //result
408 | //14
409 | ```
410 |
411 | Every time you add an expression to nerdamer it's stored. To list the expressions currently in nerdamer call
412 | the 'expressions' method. To delete an expression use the 'clear' method and pass in the expression you want to delete.
413 | To clear everything pass in the string 'all'.
414 |
415 | ```javascript
416 | nerdamer('n*R*T/v');
417 | nerdamer('mc^2');
418 | nerdamer('G*m1*m2/d^2');
419 |
420 | nerdamer.clear(2);
421 |
422 | console.log(nerdamer.expressions(true));
423 |
424 | //result:
425 | //{ '1': 'R*T*n*v^(-1)', '2': 'G*d^(-2)*m1*m2' }
426 |
427 | nerdamer.clear('all');
428 | console.log(nerdamer.expressions(true));
429 | //result:
430 | //{}
431 | ```
432 |
433 | If you need go get the variables of an expression use the variables method. This method can be called after
434 | nerdamer was provided an expression. For example
435 |
436 | ```javascript
437 | var variables = nerdamer('csc(x*cos(y))-no_boring_x').variables();
438 | console.log(variables);
439 | //result:
440 | //[ 'no_boring_x', 'x', 'y' ]
441 | ```
442 |
443 | The order in which the variables appear require a little bit of knowledge of how nerdamer organizes symbols. For the
444 | sake of simplicity we'll just assume that there is no particular order
445 |
446 | ----------------------------------------------------------------------------------------------------------------------
447 |
448 | Using the solver
449 | ===============
450 | To solve equations first load Solve.js. Just remember that Solve also required Algebra.js and Calculus.js to be loaded. You can then solve equations using nerdamer. Important: State the variable for which you are trying to solve.
451 | ```javascript
452 | var sol = nerdamer.solveEquations('x^3+8=x^2+6','x');
453 | console.log(sol.toString());
454 | //1+i,-i+1,-1
455 | ```
456 |
457 | Notice that we use toString rather than text as this returns a javascript array.
458 |
459 | You can also solve an expression
460 | ```javascript
461 | var e = nerdamer.solveEquations('x^2+4-y', 'y');
462 | console.log(e[0].text());
463 | //4+x^2
464 | ```
465 |
466 | You can also solve multivariate equations
467 | ```javascript
468 | var sol = nerdamer.solveEquations('x^2+8+y=x+6','x');
469 | console.log(sol.toString());
470 | //0.5*((-4*y-7)^0.5+1),0.5*(-(-4*y-7)^0.5+1)
471 | ```
472 | You can do up to 3rd order polynomials for multivariate polynomials
473 |
474 | Additionally you can try for equations containing functions. This is more of a hit or miss approach unlike single variable polynomials (which uses Mr. David Binner's Jenkins-Traub port - http://www.akiti.ca/PolyRootRe.html) but it's there if you want to give it a try.
475 |
476 | ```javascript
477 | var sol = nerdamer.solveEquations('cos(x)+cos(3*x)=1','x');
478 | console.log(sol.toString());
479 | //5.7981235959208695,0.4850617112587174
480 | ```
481 | To solve a system of linear equations pass them in as an array. For example
482 |
483 | ```javascript
484 | var sol = nerdamer.solveEquations(['x+y=1', '2*x=6', '4*z+y=6']);
485 | console.log(sol);
486 | //[ [ 'x', 3 ], [ 'y', -2 ], [ 'z', 2 ] ]
487 | ```
488 | In version 0.7.2 and up the solver can additionally be used in the following way
489 | ```javascript
490 | //first parse the equation
491 | var x = nerdamer('x^2+2=y-7*a');
492 | //You can make substitutions to the equation
493 | x = x.evaluate({a: 'x^2-3'});
494 | console.log(x.toString()); //2+x^2=-7*x^2+21+y
495 | var solutions = x.solveFor('x');
496 | console.log(solutions.toString()); //(1/16)*sqrt(32*y+608),(-1/16)*sqrt(32*y+608)
497 | ```
498 |
--------------------------------------------------------------------------------
/all.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Author : Martin Donk
3 | * Website : http://www.nerdamer.com
4 | * Email : martin.r.donk@gmail.com
5 | * Source : https://github.com/jiggzson/nerdamer
6 | * Can be used to load all add-ons with one require
7 | */
8 |
9 | var nerdamer = require('./nerdamer.core.js');
10 | require('./Algebra.js');
11 | require('./Calculus.js');
12 | require('./Solve.js');
13 | require('./Extra.js');
14 |
15 | //export nerdamer
16 | module.exports = nerdamer;
17 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp');
2 | var concat = require('gulp-concat');
3 | var uglify = require('gulp-uglify');
4 |
5 | var devFiles = ['./nerdamer.core.js','./Algebra.js', './Calculus.js', './Solve.js', './Extra.js'];
6 |
7 | gulp.task('concat_all', function() {
8 | return gulp.src(devFiles)
9 | .pipe(concat('all.min.js'))
10 | .pipe(uglify())
11 | .pipe(gulp.dest('./'));
12 | });
13 |
14 | gulp.task('watch', function() {
15 | gulp.watch(devFiles, gulp.series('concat_all'));
16 | });
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | export as namespace nerdamer
2 | export = nerdamer
3 | declare function nerdamer(
4 | expression: nerdamer.ExpressionParam,
5 | subs?: { [name: string]: string },
6 | option?: keyof nerdamer.Options | (keyof nerdamer.Options)[],
7 | location?: nerdamer.int): nerdamer.Expression
8 | declare namespace nerdamer {
9 | export type ExpressionParam = Expression | string
10 | export interface Options {
11 | numer: never, expand: never
12 | }
13 | type int = number
14 | /**
15 | * Returns the current version of nerdamer.
16 | */
17 | export function version(): string
18 |
19 | /**
20 | * Sets a constant value which nerdamer will automatically substitute when parsing expression/equation
21 | * @param name The variable to be set as the constant.
22 | * @param value The value for the expression to be set to.
23 | */
24 | export function setConstant(name: string, value: number | string): typeof nerdamer
25 |
26 | /**
27 | * Sets a function which can then be called using nerdamer.
28 | * @param fnName The function name
29 | * @param fnParams The parameter array in the order in which the arguments are to be passed
30 | * @param fnBody The body of the function
31 | * @example 1
32 | * nerdamer.setFunction('f', ['x', 'y'], 'x^2+y')
33 | * var x = nerdamer('f(4, 7)').toString()
34 | * console.log(x.toString())
35 | * nerdamer.setFunction('g', ['z', 'x', 'y'], '2*x+3*y+4*z')
36 | * x = nerdamer('g(3, 1, 2)')
37 | * console.log(x.toString())
38 | * @example 2
39 | * nerdamer.setFunction('f(x, y) = x^2+y') //OR 'f(x, y) := x^2+y'
40 | * var x = nerdamer('f(4, 7)').toString()
41 | * console.log(x.toString())
42 | * nerdamer.setFunction('g', ['z', 'x', 'y'], '2*x+3*y+4*z')
43 | * x = nerdamer('g(3, 1, 2)')
44 | * console.log(x.toString())
45 | * @example 3
46 | * function custom(x , y) {
47 | * return x + y;
48 | * }
49 | * nerdamer.setFunction(custom);
50 | * var x = nerdamer('custom(4, 7)').toString();
51 | * console.log(x.toString())
52 | * console.log(x.valueOf())
53 | * OR just nerdamer.setFunction(function custom(x , y) { return x + y; });
54 | */
55 | export function setFunction(fnName: string | Function, fnParams?: string[], fnBody?: string): typeof nerdamer
56 |
57 | /**
58 | * Returns the nerdamer core object. This object contains all the core functions of nerdamer and houses the parser.
59 | * @example
60 | * Object.keys(nerdamer.getCore())
61 | */
62 | export function getCore(): any
63 |
64 | /**
65 | * Gets the list of reserved names. This is a list of names already in use by nerdamer excluding variable names. This is not a static list.
66 | * @param asArray Pass in true to get the list back as an array instead of as an object.
67 | */
68 | export function reserved(asArray: true): string[]
69 | export function reserved(asArray?: false): any
70 |
71 | /**
72 | * Clears all stored expressions.
73 | * @example
74 | * var x = nerdamer('x*x')
75 | console.log(nerdamer.expressions())
76 | nerdamer.flush() //clear all expressions
77 | console.log(nerdamer.expressions())
78 | */
79 | export function flush(): typeof nerdamer
80 |
81 | /**
82 | * Converts and expression to LaTeX without evaluating expression.
83 | * @param expression The expression being converted.
84 | */
85 | export function convertToLaTeX(expression: string): string
86 |
87 | /**
88 | * Attempts to import a LaTeX string.
89 | * @param TeX The expression being converted.
90 | */
91 | export function convertFromLaTeX(TeX: string): Expression
92 |
93 | /**
94 | * Each time an expression is parsed nerdamer stores the result. Use this method to get back stored expressions.
95 | * @param asObject Pass in true to get expressions as numbered object with 1 as starting index
96 | * @param asLatex Pass in the string "LaTeX" to get the expression to LaTeX, otherwise expressions come back as strings
97 | */
98 | export function expressions(asObject?: boolean, asLatex?: 'LaTeX'): string[]
99 |
100 | /**
101 | * Registers a module function with nerdamer. The object needs to contain at a minimum, a name property (text), a numargs property (int), this is -1 for variable arguments or an array containing the min and max arguments, the visible property (bool) which allows use of this function through nerdamer, defaults to true, and a build property containing a function which returns the function to be used. This function is also handy for creating aliases to functions. See below how the alias D was created for the diff function).
102 | * @param o
103 | */
104 | export interface ModuleFunction {
105 | /**
106 | * Name of function.
107 | */
108 | name: string,
109 |
110 | /**
111 | * Number of function arguments, -1 for variable arguments or an tuple containing minimum and maximum number of arguments.
112 | */
113 | numargs: int | [int, int],
114 |
115 | /**
116 | * Allows use of this function through nerdamer. Defaults to true.
117 | */
118 | visible?: boolean,
119 |
120 | /**
121 | * Return the function to be used.
122 | */
123 | build(): (...args: number[]) => number
124 | }
125 |
126 | /**
127 | * Registers one or more module functions with nerdamer.
128 | * @param f
129 | * @example
130 | * var core = nerdamer.getCore()
131 | var _ = core.PARSER
132 | function f(a, b) {
133 | //use clone for safety since a or b might be returned
134 | var sum = _.add(a.clone(), b.clone())
135 | var product = _.multiply(a.clone(), b.clone())
136 | return _.multiply(sum, product)
137 | }
138 | //register the function with nerdamer
139 | nerdamer.register({
140 | name: 'myFunction',
141 | numargs: 2,
142 | visible: true,
143 | build: function(){ return f }
144 | })
145 |
146 | //create an alias for the diff function
147 | var core = nerdamer.getCore()
148 | nerdamer.register({
149 | name: 'D',
150 | visible: true,
151 | numargs: [1, 3],
152 | build: function(){ return core.Calculus.diff }
153 | })
154 | */
155 | export function register(f: ModuleFunction | ModuleFunction[]): typeof nerdamer
156 |
157 | /**
158 | * This method can be used to check that the variable meets variable name requirements for nerdamer. Variable names Must start with a letter or underscore and may contains any combination of numbers, letters, and underscores after that.
159 | * @param variable_name The variable name being validated.
160 | * @example
161 | * nerdamer.validVarName('cos') // false
162 | * nerdamer.validVarName('chicken1') // true
163 | * nerdamer.validVarName('1chicken') // false
164 | * nerdamer.validVarName('_') // true
165 | */
166 | export function validVarName(variable_name: string): boolean
167 |
168 | /**
169 | * Sets a known value in nerdamer. This differs from setConstant as the value can be overridden trough the scope. See example.
170 | * @param name The known value to be set.
171 | * @param value The value for the expression to be set to
172 | * @example
173 | * nerdamer.setVar('x', '11')
174 | * nerdamer('x*x') // == 121
175 | * // nerdamer will use 13 instead of 11:
176 | * nerdamer('x*x', {x: 13}) // == 169
177 | * // the value will be 121 again since the known value isn't being overridden:
178 | * nerdamer('x*x') // == 121
179 | * nerdamer.setVar('x', 'delete')
180 | * // since there no longer is a known value it will just be evaluated symbolically:
181 | * nerdamer('x*x') // == x^2
182 | */
183 | export function setVar(name: string, value: number | string | 'delete'): void
184 |
185 | /**
186 | * Clears all previously set variables.
187 | */
188 | export function clearVars(): typeof nerdamer
189 |
190 | /**
191 | * Gets all previously set variables.
192 | * @param option Use "LaTeX" to get as LaTeX. Defaults to text.
193 | */
194 | export function getVars(option: 'LaTeX' | 'text'): { [name: string]: string }
195 |
196 | /**
197 | * Sets the value of a nerdamer setting. Currently PARSE2NUMBER and IMAGINARY. Setting PARSE2NUMBER to true will let nerdamer always try to return a number whenenver possible. IMAGINARY allows you to change the variable used for imaginary to j for instance.
198 | * @param setting The setting to be changed
199 | * @param value The value to set the setting to.
200 | * @example
201 | * nerdamer.set('PARSE2NUMBER', true)
202 | * nerdamer('cos(9)+1') // == 14846499/167059106
203 | * nerdamer.set('IMAGINARY', 'j')
204 | * nerdamer('sqrt(-1)') // == j
205 | */
206 | export function set(setting: 'PARSE2NUMBER', value: boolean): typeof nerdamer
207 | export function set(setting: 'IMAGINARY', value: string | 'i'): typeof nerdamer
208 | export function set(setting: 'POWER_OPERATOR', value: '**' | '^'): typeof nerdamer
209 |
210 | export interface Expression {
211 | /**
212 | * Generates a JavaScript function given the expression. This is perfect for plotting and filtering user input. Plotting for the demo is accomplished using this. The order of the parameters is in alphabetical order by default but an argument array can be provided with the desired order.
213 | * @param args_array The argument array with the order in which they are preferred.
214 | */
215 | buildFunction(args_array?: string[]): (...args: number[]) => number
216 |
217 | /**
218 | * Forces evaluation of the expression.
219 | * @example
220 | * const x = nerdamer('sin(9+5)')
221 | * //the expression is simplified but the functions aren't called:
222 | * x.toString() // == sin(14)
223 | * // force function calls with evaluate:
224 | * x.evaluate().toString() // == 127690464/128901187
225 | */
226 | evaluate(): Expression
227 |
228 | /**
229 | * Substitutes a given value for another given value
230 | * @param value The value being substituted.
231 | * @param for_value The value to substitute for.
232 | */
233 | sub(value: string, for_value: string): Expression
234 |
235 | /**
236 | * Get a list of the variables contained within the expression.
237 | */
238 | variables(): string[]
239 |
240 | /**
241 | * Gets expression as LaTeX
242 | */
243 | toTeX(): string
244 |
245 | /**
246 | * Returns the value of the expression as a string or a number
247 | */
248 | valueOf(): string | number
249 |
250 | /**
251 | * Gets the list of reserved names. This is a list of names already in use by nerdamer excluding variable names. This is not a static list.
252 | * @param outputType Pass in the string 'decimals' to always get back numers as decimals. Pass in the string 'fractions' to always get back number as fractions. Defaults to decimals.
253 | */
254 | text(outputType?: 'decimals' | 'fractions'): string
255 |
256 | /**
257 | * This method requires that the Solve, Calculus, and Algebra add-ons are loaded. It will attempt to solve an equation. If no solutions are found then an empty array is returned. It can solve for multivariate polynomials up to the third degree. After which it can solve numerically for polynomials up to the 100th degree. If it's a univariate equation it will attempt to solve it using numerical methods.
258 | * @param variable The variable to solve for.
259 | * @example
260 | * nerdamer('a*x^2+b*x=y').evaluate({y: 'x-7'}) // == ??
261 | * eq.solveFor('x') // ?? TODO
262 | */
263 | solveFor(variable: string): Expression | Expression[]
264 |
265 | /**
266 | * Forces the expression to displayed with decimals
267 | */
268 | toDecimal(prec?: number): string
269 |
270 | /**
271 | * Checks to see if the expression's value equals a number. Compares the direct value returned.
272 | * The function will not check for all possible cases. To avoid this call evaluate.
273 | * @example
274 | * nerdamer('sqrt(5)').isNumber()
275 | * // false
276 | * nerdamer('sqrt(5)').evaluate().isNumber()
277 | * // true
278 | */
279 | isNumber(): boolean
280 |
281 | /**
282 | * Checks if a number evaluates to an imaginary number
283 | * @example
284 | * nerdamer('sqrt(-5)+8').isImaginary()
285 | * // true
286 | * nerdamer('sqrt(5)+8').isImaginary()
287 | * // false
288 | */
289 | isImaginary(): boolean
290 |
291 | /**
292 | * Adds a value to an expression
293 | * @example
294 | * nerdamer('x').add(3)
295 | */
296 | add(symbol: number | string | Expression): Expression
297 |
298 | /**
299 | * Subtracts a value from an expression
300 | * @example
301 | * nerdamer('x').subtract(3)
302 | */
303 | subtract(symbol: number | string | Expression): Expression
304 |
305 | /**
306 | * Multiplies an expression by a value
307 | * @example
308 | * nerdamer('x').multiply(3)
309 | */
310 | multiply(symbol: number | string | Expression): Expression
311 |
312 | /**
313 | * Divides an expression by a valule
314 | * @example
315 | * nerdamer('9*x').divide(3)
316 | */
317 | divide(symbol: number | string | Expression): Expression
318 |
319 | /**
320 | * Raises an expression to a power
321 | * @example
322 | * nerdamer('x').pow(3)
323 | */
324 | pow(symbol: number | string | Expression): Expression
325 |
326 | /**
327 | * Checks if two values are equal
328 | * @param value The value being tested
329 | * @example
330 | * nerdamer('sqrt(9)').eq(3)
331 | * // true
332 | * nerdamer('x').eq('y')
333 | * // false
334 | */
335 | eq(value: number | string | Expression): boolean
336 |
337 | /**
338 | * Checks if a value is less than another
339 | * @param value The value being tested
340 | * @example
341 | * nerdamer('sqrt(9)').lt(3)
342 | * // false
343 | * nerdamer('8').lt(100)
344 | * // true
345 | */
346 | lt(value: number | string | Expression): boolean
347 |
348 | /**
349 | * Checks if a value is less than or equal to another
350 | * @param value The value being tested
351 | * @example
352 | * nerdamer('sqrt(9)').lte(3)
353 | * // true
354 | * nerdamer('x').lte(100)
355 | * // false
356 | */
357 | lte(value: number | string | Expression): boolean
358 |
359 | /**
360 | * Checks if a value is greater than another
361 | * @param value The value being tested
362 | * @example
363 | * nerdamer('sqrt(9)').gt(3)
364 | * // false
365 | * nerdamer('800').gt(100)
366 | * // true
367 | */
368 | gt(value: number | string | Expression): boolean
369 |
370 | /**
371 | * Checks if a value is greater than or equal to another
372 | * @param value The value being tested
373 | * @example
374 | * nerdamer('sqrt(9)').gte(3)
375 | * // true
376 | * nerdamer('x').gte(100)
377 | * // false
378 | */
379 | gte(value: number | string | Expression): boolean
380 |
381 | /**
382 | * Expands a function or expression.
383 | * @example
384 | * nerdamer('x*(x+1)').expand();
385 | * // x+x^2
386 | * nerdamer('(x+y)*(x-5)*x').expand();
387 | * // -5*x*y-5*x^2+x^3+x^2*y
388 | */
389 | expand(): Expression
390 | }
391 |
392 | ////////// CALCULUS
393 |
394 | /**
395 | * Gets the GCD of 2 polynomials
396 | * @param expression Returns the appropriate value if possible otherwise it returns the function with the simplified expression.
397 | * @param index The index of summation.
398 | * @param lower Starting index.
399 | * @param upper Ending index.
400 | */
401 | export function sum(expression: ExpressionParam,
402 | index: string,
403 | lower: ExpressionParam,
404 | upper: ExpressionParam): Expression
405 |
406 | /**
407 | *
408 | * @param expression Returns the appropriate value if possible otherwise it returns the function with the simplified expression.
409 | * @param variable The variable with respect to which to integrate.
410 | */
411 | export function integrate(expression: ExpressionParam, variable: string): Expression
412 |
413 | /**
414 | *
415 | * @param expression Returns the appropriate value if possible otherwise it returns the function with the simplified expression.
416 | * @param variable The variable with respect to which to differentiate.
417 | * @param n Calculate the nth derivative.
418 | */
419 | export function diff(expression: ExpressionParam, variable: string, n?: int): Expression
420 |
421 | ////////// ALGEBRA
422 |
423 | /**
424 | * Divides 2 polynominals.
425 | * @param expression Returns the appropriate value if possible otherwise it returns the function with the simplified expression.
426 | */
427 | export function divide(expression: ExpressionParam): Expression
428 |
429 | /**
430 | * Factor an expression.
431 | * @param expression Returns the appropriate value if possible otherwise it returns the function with the simplified expression.
432 | */
433 | export function factor(expression: ExpressionParam): Expression
434 |
435 | /**
436 | * Gets the GCD of 2 polynomials.
437 | * @param expression Returns the appropriate value if possible otherwise it returns the function with the simplified expression.
438 | */
439 | export function gcd(expression: ExpressionParam): Expression
440 |
441 | /**
442 | * Finds the roots of a univariate polynomial.
443 | * @param expression
444 | */
445 | export function factor(expression: ExpressionParam): Expression
446 | }
447 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |