Skip to content

Commit

Permalink
Implement the points method
Browse files Browse the repository at this point in the history
  • Loading branch information
Amphiluke committed Jul 15, 2018
1 parent 99676f9 commit 7521176
Show file tree
Hide file tree
Showing 25 changed files with 2,965 additions and 1,371 deletions.
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,40 @@ let rSqr = potprox.utils.rSqr(data, morse);
console.log(`Coefficient of determination = ${rSqr}`);
```

#### `potprox.utils.points(potential [, options])`

The method `potprox.utils.points()` can be used to generate points of a potential function in the given distance range. The method can take one or two arguments and returns a [Generator object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator) which you may iterate over. The first parameter of the method is the approximating potential instance, and the second one (optional) is the configuration object. The following configuration options are available (each of them is optional):

* `start` — starting interatomic distance to generate points from (by default it’s set to a half of the equilibrium distance);
* `end` — end interatomic distance where to stop (by default it’s double of the equilibrium distance);
* `step` — step for point generation (default step is configured to generate 50 points).

```javascript
let morse = new potprox.Morse({d0: 0.0368, r0: 5.316, a: 0.867});

// Generate 50 points starting from r = r0/2 and finishing at r = 2*r0
for (let {r, e, index} of potprox.utils.points(morse)) {
console.log(`${index + 1}. r = ${r.toFixed(4)} nm; E = ${e.toFixed(3)} eV`);
}

// Generate 30 points in the user-defined distance range
let start = 5.0;
let end = 8.5;
let pointCount = 30;
let step = (end - start) / (pointCount - 1);
for (let {r, e, index} of potprox.utils.points(morse, {start, end, step})) {
console.log(`${index + 1}. r = ${r.toFixed(4)} nm; E = ${e.toFixed(3)} eV`);
}

// Generate points infinitely until the given energy threshold is reached
for (let {r, e, index} of potprox.utils.points(morse, {start: 5.0, end: Infinity, step: 0.1})) {
console.log(`${index + 1}. r = ${r.toFixed(4)} nm; E = ${e.toFixed(5)} eV`);
if (e > -0.001) {
break;
}
}
```

## Tips

The overridden method `toJSON()` allows the instances of the potprox potential classes to be easily serialized to a JSON string, and restored from the JSON string later on.
Expand Down
9 changes: 9 additions & 0 deletions ava.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export default {
files: ["test/**/*-spec.mjs"],
sources: ["dist/potprox.mjs"],
babel: {
extensions: ["mjs"]
},
require: ["esm"],
verbose: true
};
23 changes: 22 additions & 1 deletion dist/potprox.js
Original file line number Diff line number Diff line change
Expand Up @@ -853,7 +853,7 @@ https://amphiluke.github.io/potprox/
/**
* Calculate the coefficient of determination to measure the goodness of fit
* @param {Array.<{r: Number, e: Number}>} data - Experimental/ab initio data
* @param {Object} potential - Approximating potential
* @param {Object} potential - Approximating potential instance
* @returns {Number}
* @see https://en.wikipedia.org/wiki/Coefficient_of_determination
*/
Expand All @@ -872,6 +872,27 @@ https://amphiluke.github.io/potprox/
ssTot += diff * diff;
}
return 1 - ssRes / ssTot;
},

/**
* Generate points of the potential curve
* @param {Object} potential - Approximating potential instance
* @param {Object} [options] - Configuration options
* @param {Number} [options.start=potential.r0/2] - Starting interatomic distance
* @param {Number} [options.end=potential.r0*2] - End interatomic distance
* @param {Number} [options.step=(end-start)/49] - Step for point generation (defaults make 50 points)
* @returns {Generator<{r: Number, e: Number}>}
*/
* points(potential, {start = potential.r0 / 2, end = potential.r0 * 2, step = (end - start) / 49} = {}) {
let i = 0;
let r = start;
let direction = Math.sign(end - start); // when end < start, iteration is backward
step = Math.abs(step) * direction; // the user may specify step as signed or not
while ((end - r) * direction >= 0) {
yield {r, e: potential.at(r), index: i};
r = start + step * ++i;
}
return {r: end, e: potential.at(end)};
}
};

Expand Down
2 changes: 1 addition & 1 deletion dist/potprox.min.js

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions dist/potprox.min.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/*!
potprox v0.6.0
https://amphiluke.github.io/potprox/
*/
let e=new WeakMap;class r{constructor({epsilon:r=1,sigma:t=1}={}){e.set(this,{}),this.epsilon=r,this.sigma=t}static get type(){return"LennardJones"}static from(e){if(!Array.isArray(e))throw new TypeError("Approximated data should be an array of points");if(e.length<3)throw new Error("Too little points. Approximation is impossible");let t=0,a=0,o=0,i=0,s=0;for(let{r:r,e:n}of e)t+=Math.pow(r,-24),a+=Math.pow(r,-18),o+=n*Math.pow(r,-12),i+=Math.pow(r,-12),s+=n*Math.pow(r,-6);let n=(s-a*o/t)/(i-a*a/t),h=(o-a*n)/t,l=Math.pow(-h/n,1/6),p=h/(4*Math.pow(l,12));return new r({epsilon:p,sigma:l})}get epsilon(){return e.get(this).epsilon}set epsilon(r){if(!Number.isFinite(r))throw new TypeError("The 'epsilon' parameter should be a finite number");if(r<=0)throw new RangeError("The 'epsilon' parameter should be greater than zero");e.get(this).epsilon=r}get sigma(){return e.get(this).sigma}set sigma(r){if(!Number.isFinite(r))throw new TypeError("The 'sigma' parameter should be a finite number");if(r<=0)throw new RangeError("The 'sigma' parameter should be greater than zero");e.get(this).sigma=r}get r0(){return 1.122462048309373*this.sigma}set r0(e){this.sigma=e/1.122462048309373}at(e){if("number"!=typeof e)throw new TypeError("Distance should be a number");if(e<0)throw new RangeError("Distance shouldn't be less than zero");let{epsilon:r,sigma:t}=this;return 4*r*(Math.pow(t/e,12)-Math.pow(t/e,6))}toJSON(){return{type:r.type,epsilon:this.epsilon,sigma:this.sigma}}}let t=new WeakMap;class a{constructor({d0:e=1,r0:r=1,a:a=2}={}){t.set(this,{}),this.d0=e,this.r0=r,this.a=a}static get type(){return"Buckingham"}static fastFrom(e){if(!Array.isArray(e))throw new TypeError("Approximated data should be an array of points");if(e.length<3)throw new Error("Too little points. Approximation is impossible");e=e.slice().sort((e,r)=>e.r-r.r);let r,t,o,i=Number.POSITIVE_INFINITY,s=1;for(let{r:r,e:t}of e)t<i&&(i=t,s=r);i=Math.abs(i);for(let a=1;a<e.length&&(r=e[a-1],!((t=e[a]).r>=s||r.e<0||t.e<0));a++);if(r&&t&&r.r<s&&t.r<=s){let e=r.e*(r.r-t.r)/(t.e-r.e)+r.r;if(e>0){let r=1-e/s,t=Math.pow(s/e,6)/6;o=(t-r-Math.sqrt(t*t-2*r*t-r*r))/(r*r),Number.isFinite(o)||(o=void 0)}}return new a({d0:i,r0:s,a:o})}static from(e,{d0Conv:r=.001,r0Conv:t=.001,aConv:a=.001}={}){let o=this.fastFrom(e),{d0:i,r0:s,a:n}=o;const h=i*r,l=s*t,p=n*a;let b,u,d;do{let r=0,t=0,a=0,o=0,h=0,l=0,p=0,w=0,f=0;for(let{r:b,e:u}of e){let e=n*Math.pow(s/b,6),d=Math.exp(n*(1-b/s)),m=i/(n-6)*(6*d-e),g=m/i,y=i/(n-6)*(6*d*n*b/(s*s)-6*e/s),T=-i/((n-6)*(n-6))*(6*d-e)+i/(n-6)*(6*(1-b/s)*d-e/n);r+=g*g,t+=y*g,a+=T*g,o+=(m-u)*g,h+=y*y,l+=T*y,p+=(m-u)*y,w+=T*T,f+=(m-u)*T}i+=b=(-t*(u=((a-r*l/t)*(d=-(o-r*p/t-(t-r*h/t)/(t-r*l/a)*(o-r*f/a))/(a-r*l/t-(a-r*w/a)*(t-r*h/t)/(t-r*l/a)))+(o-r*p/t))/(r*h/t-t))-a*d-o)/r,s+=u,n+=d}while(Math.abs(b)>h&&Math.abs(u)>l&&Math.abs(d)>p);return o.d0=i,o.r0=s,o.a=n,o}get d0(){return t.get(this).d0}set d0(e){if(!Number.isFinite(e))throw new TypeError("The 'd0' parameter should be a finite number");if(e<=0)throw new RangeError("The 'd0' parameter should be greater than zero");t.get(this).d0=e}get r0(){return t.get(this).r0}set r0(e){if(!Number.isFinite(e))throw new TypeError("The 'r0' parameter should be a finite number");if(e<=0)throw new RangeError("The 'r0' parameter should be greater than zero");t.get(this).r0=e}get a(){return t.get(this).a}set a(e){if(!Number.isFinite(e))throw new TypeError("The 'a' parameter should be a finite number");if(e<=0)throw new RangeError("The 'a' parameter should be greater than zero");t.get(this).a=e}at(e){if("number"!=typeof e)throw new TypeError("Distance should be a number");if(e<0)throw new RangeError("Distance shouldn't be less than zero");let{d0:r,r0:t,a:a}=this;return r/(a-6)*(6*Math.exp(a*(1-e/t))-a*Math.pow(t/e,6))}toJSON(){return{type:a.type,d0:this.d0,r0:this.r0,a:this.a}}}let o=new WeakMap;class i{constructor({d0:e=1,r0:r=1,a:t=1}={}){o.set(this,{}),this.d0=e,this.r0=r,this.a=t}static get type(){return"Morse"}static fastFrom(e){if(!Array.isArray(e))throw new TypeError("Approximated data should be an array of points");if(e.length<3)throw new Error("Too little points. Approximation is impossible");let r=Number.POSITIVE_INFINITY,t=1;for(let{r:a,e:o}of e)o<r&&(r=o,t=a);r=Math.abs(r);let a=0,o=0;for(let{r:i,e:s}of e){let e=Math.sqrt(1+s/r),n=Number.NaN;i>t?n=Math.log(1-e)/(t-i):i<t&&(n=Math.log(1+e)/(t-i)),Number.isFinite(n)&&(a+=n,o++)}return new i({d0:r,r0:t,a:a/=o})}static from(e,{d0Conv:r=.001,r0Conv:t=.001,aConv:a=.001}={}){let o=this.fastFrom(e),{d0:i,r0:s,a:n}=o;const h=i*r,l=s*t,p=n*a;let b,u,d;do{let r=0,t=0,a=0,o=0,h=0,l=0,p=0,w=0,f=0;for(let{r:b,e:u}of e){let e=Math.exp(n*(s-b)),d=i*(1-e)*(1-e)-i,m=d/i,g=-2*i*(1-e)*n*e,y=2*i*(1-e)*(b-s)*e;r+=m*m,t+=g*m,a+=y*m,o+=(d-u)*m,h+=g*g,l+=y*g,p+=(d-u)*g,w+=y*y,f+=(d-u)*y}i+=b=(-t*(u=((a-r*l/t)*(d=-(o-r*p/t-(t-r*h/t)/(t-r*l/a)*(o-r*f/a))/(a-r*l/t-(a-r*w/a)*(t-r*h/t)/(t-r*l/a)))+(o-r*p/t))/(r*h/t-t))-a*d-o)/r,s+=u,n+=d}while(Math.abs(b)>h&&Math.abs(u)>l&&Math.abs(d)>p);return o.d0=i,o.r0=s,o.a=n,o}get d0(){return o.get(this).d0}set d0(e){if(!Number.isFinite(e))throw new TypeError("The 'd0' parameter should be a finite number");if(e<=0)throw new RangeError("The 'd0' parameter should be greater than zero");o.get(this).d0=e}get r0(){return o.get(this).r0}set r0(e){if(!Number.isFinite(e))throw new TypeError("The 'r0' parameter should be a finite number");if(e<=0)throw new RangeError("The 'r0' parameter should be greater than zero");o.get(this).r0=e}get a(){return o.get(this).a}set a(e){if(!Number.isFinite(e))throw new TypeError("The 'a' parameter should be a finite number");if(e<=0)throw new RangeError("The 'a' parameter should be greater than zero");o.get(this).a=e}at(e){if("number"!=typeof e)throw new TypeError("Distance should be a number");if(e<0)throw new RangeError("Distance shouldn't be less than zero");let{d0:r,r0:t,a:a}=this,o=1-Math.exp(a*(t-e));return r*o*o-r}toJSON(){return{type:i.type,d0:this.d0,r0:this.r0,a:this.a}}}let s=new WeakMap;class n{constructor({d0:e=1,r0:r=1,b:t=2}={}){s.set(this,{}),this.d0=e,this.r0=r,this.b=t}static get type(){return"Rydberg"}static fastFrom(e){if(!Array.isArray(e))throw new TypeError("Approximated data should be an array of points");if(e.length<3)throw new Error("Too little points. Approximation is impossible");e=e.slice().sort((e,r)=>e.r-r.r);let r,t,a,o=Number.POSITIVE_INFINITY,i=1;for(let{r:r,e:t}of e)t<o&&(o=t,i=r);o=Math.abs(o);for(let a=1;a<e.length&&(r=e[a-1],!((t=e[a]).r>=i||r.e<0||t.e<0));a++);if(r&&t&&r.r<i&&t.r<=i){let e=r.e*(r.r-t.r)/(t.e-r.e)+r.r;e>0&&(a=i/(i-e))}return new n({d0:o,r0:i,b:a})}static from(e,{d0Conv:r=.001,r0Conv:t=.001,bConv:a=.001}={}){let o=this.fastFrom(e),{d0:i,r0:s,b:n}=o;const h=i*r,l=s*t,p=n*a;let b,u,d;do{let r=0,t=0,a=0,o=0,h=0,l=0,p=0,w=0,f=0;for(let{r:b,e:u}of e){let e=n*(b/s-1),d=Math.exp(-e),m=-i*(1+e)*d,g=m/i,y=-i*n*b/(s*s)*d*e,T=i*e/n*d*e;r+=g*g,t+=y*g,a+=T*g,o+=(m-u)*g,h+=y*y,l+=T*y,p+=(m-u)*y,w+=T*T,f+=(m-u)*T}i+=b=(-t*(u=((a-r*l/t)*(d=-(o-r*p/t-(t-r*h/t)/(t-r*l/a)*(o-r*f/a))/(a-r*l/t-(a-r*w/a)*(t-r*h/t)/(t-r*l/a)))+(o-r*p/t))/(r*h/t-t))-a*d-o)/r,s+=u,n+=d}while(Math.abs(b)>h&&Math.abs(u)>l&&Math.abs(d)>p);return o.d0=i,o.r0=s,o.b=n,o}get d0(){return s.get(this).d0}set d0(e){if(!Number.isFinite(e))throw new TypeError("The 'd0' parameter should be a finite number");if(e<=0)throw new RangeError("The 'd0' parameter should be greater than zero");s.get(this).d0=e}get r0(){return s.get(this).r0}set r0(e){if(!Number.isFinite(e))throw new TypeError("The 'r0' parameter should be a finite number");if(e<=0)throw new RangeError("The 'r0' parameter should be greater than zero");s.get(this).r0=e}get b(){return s.get(this).b}set b(e){if(!Number.isFinite(e))throw new TypeError("The 'b' parameter should be a finite number");if(e<=1)throw new RangeError("The 'b' parameter should be greater than 1");s.get(this).b=e}at(e){if("number"!=typeof e)throw new TypeError("Distance should be a number");if(e<0)throw new RangeError("Distance shouldn't be less than zero");let{d0:r,r0:t,b:a}=this,o=a*(e-t)/t;return-r*(1+o)*Math.exp(-o)}toJSON(){return{type:n.type,d0:this.d0,r0:this.r0,b:this.b}}}let h=new WeakMap;class l{constructor({d0:e=1,r0:r=1,b:t=1}={}){h.set(this,{}),this.d0=e,this.r0=r,this.b=t}static get type(){return"Varshni3"}static fastFrom(e){if(!Array.isArray(e))throw new TypeError("Approximated data should be an array of points");if(e.length<3)throw new Error("Too little points. Approximation is impossible");let r=Number.POSITIVE_INFINITY,t=1;for(let{r:a,e:o}of e)o<r&&(r=o,t=a);r=Math.abs(r);let a=0,o=0;for(let{r:i,e:s}of e){let e=Math.sqrt(1+s/r),n=Number.NaN;i>t?n=Math.log(i/t*(1-e))/(t*t-i*i):i<t&&(n=Math.log(i/t*(1+e))/(t*t-i*i)),Number.isFinite(n)&&(a+=n,o++)}return new l({d0:r,r0:t,b:a/=o})}static from(e,{d0Conv:r=.001,r0Conv:t=.001,bConv:a=.001}={}){let o=this.fastFrom(e),{d0:i,r0:s,b:n}=o;const h=i*r,l=s*t,p=n*a;let b,u,d;do{let r=0,t=0,a=0,o=0,h=0,l=0,p=0,w=0,f=0;for(let{r:b,e:u}of e){let e=s/b*Math.exp(n*(s*s-b*b)),d=i*(1-e)*(1-e)-i,m=d/i,g=2*i*(1-e)*(-e/s-2*e*n*s),y=2*i*(1-e)*e*(b*b-s*s);r+=m*m,t+=g*m,a+=y*m,o+=(d-u)*m,h+=g*g,l+=y*g,p+=(d-u)*g,w+=y*y,f+=(d-u)*y}i+=b=(-t*(u=((a-r*l/t)*(d=-(o-r*p/t-(t-r*h/t)/(t-r*l/a)*(o-r*f/a))/(a-r*l/t-(a-r*w/a)*(t-r*h/t)/(t-r*l/a)))+(o-r*p/t))/(r*h/t-t))-a*d-o)/r,s+=u,n+=d}while(Math.abs(b)>h&&Math.abs(u)>l&&Math.abs(d)>p);return o.d0=i,o.r0=s,o.b=n,o}get d0(){return h.get(this).d0}set d0(e){if(!Number.isFinite(e))throw new TypeError("The 'd0' parameter should be a finite number");if(e<=0)throw new RangeError("The 'd0' parameter should be greater than zero");h.get(this).d0=e}get r0(){return h.get(this).r0}set r0(e){if(!Number.isFinite(e))throw new TypeError("The 'r0' parameter should be a finite number");if(e<=0)throw new RangeError("The 'r0' parameter should be greater than zero");h.get(this).r0=e}get b(){return h.get(this).b}set b(e){if(!Number.isFinite(e))throw new TypeError("The 'b' parameter should be a finite number");if(e<=0)throw new RangeError("The 'b' parameter should be greater than zero");h.get(this).b=e}at(e){if("number"!=typeof e)throw new TypeError("Distance should be a number");if(e<0)throw new RangeError("Distance shouldn't be less than zero");let{d0:r,r0:t,b:a}=this,o=1-t/e*Math.exp(a*(t*t-e*e));return r*o*o-r}toJSON(){return{type:l.type,d0:this.d0,r0:this.r0,b:this.b}}}let p={rSqr(e,r){let t=0,a=0;for(let{r:o,e:i}of e){t+=i;let e=i-r.at(o);a+=e*e}t/=e.length;let o=0;for(let{e:r}of e){let e=r-t;o+=e*e}return 1-a/o},*points(e,{start:r=e.r0/2,end:t=2*e.r0,step:a=(t-r)/49}={}){let o=0,i=r,s=Math.sign(t-r);for(a=Math.abs(a)*s;(t-i)*s>=0;)yield{r:i,e:e.at(i),index:o},i=r+a*++o;return{r:t,e:e.at(t)}}},b=Object.create(null);b[r.type]=r,b[a.type]=a,b[i.type]=i,b[n.type]=n,b[l.type]=l,Object.defineProperty(b,"utils",{configurable:!0,value:p});export default b;
Loading

0 comments on commit 7521176

Please sign in to comment.