OpenMD 3.0
Molecular Dynamics in the Open
Loading...
Searching...
No Matches
Polynomial.hpp
Go to the documentation of this file.
1/*
2 * Copyright (c) 2004-present, The University of Notre Dame. All rights
3 * reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * 3. Neither the name of the copyright holder nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 *
31 * SUPPORT OPEN SCIENCE! If you use OpenMD or its source code in your
32 * research, please cite the appropriate papers when you publish your
33 * work. Good starting points are:
34 *
35 * [1] Meineke, et al., J. Comp. Chem. 26, 252-271 (2005).
36 * [2] Fennell & Gezelter, J. Chem. Phys. 124, 234104 (2006).
37 * [3] Sun, Lin & Gezelter, J. Chem. Phys. 128, 234107 (2008).
38 * [4] Vardeman, Stocker & Gezelter, J. Chem. Theory Comput. 7, 834 (2011).
39 * [5] Kuang & Gezelter, Mol. Phys., 110, 691-701 (2012).
40 * [6] Lamichhane, Gezelter & Newman, J. Chem. Phys. 141, 134109 (2014).
41 * [7] Lamichhane, Newman & Gezelter, J. Chem. Phys. 141, 134110 (2014).
42 * [8] Bhattarai, Newman & Gezelter, Phys. Rev. B 99, 094106 (2019).
43 */
44
45/**
46 * @file Polynomial.hpp
47 * @author teng lin
48 * @date 11/16/2004
49 * @version 1.0
50 */
51
52#ifndef MATH_POLYNOMIAL_HPP
53#define MATH_POLYNOMIAL_HPP
54
55#include <config.h>
56
57#include <complex>
58#include <iostream>
59#include <list>
60#include <map>
61#include <utility>
62
63#include "math/Eigenvalue.hpp"
64
65namespace OpenMD {
66
67 template<typename Real>
68 Real fastpow(Real x, int N) {
69 Real result(1); // or 1.0?
70
71 for (int i = 0; i < N; ++i) {
72 result *= x;
73 }
74
75 return result;
76 }
77
78 /**
79 * @class Polynomial Polynomial.hpp "math/Polynomial.hpp"
80 * A generic Polynomial class
81 */
82 template<typename Real>
83 class Polynomial {
84 public:
85 using PolynomialType = Polynomial<Real>;
86 using ExponentType = int;
87 using CoefficientType = Real;
88 using PolynomialPairMap = std::map<ExponentType, CoefficientType>;
89 using iterator = typename PolynomialPairMap::iterator;
90 using const_iterator = typename PolynomialPairMap::const_iterator;
91
92 Polynomial() {}
93 Polynomial(Real v) { setCoefficient(0, v); }
94
95 /**
96 * Calculates the value of this Polynomial evaluated at the given x value.
97 * @return The value of this Polynomial evaluates at the given x value
98 * @param x the value of the independent variable for this
99 * Polynomial function
100 */
101 Real evaluate(const Real& x) {
102 Real result = Real();
103 ExponentType exponent;
104 CoefficientType coefficient;
105
106 for (iterator i = polyPairMap_.begin(); i != polyPairMap_.end(); ++i) {
107 exponent = i->first;
108 coefficient = i->second;
109 result += fastpow(x, exponent) * coefficient;
110 }
111
112 return result;
113 }
114
115 /**
116 * Returns the first derivative of this polynomial.
117 * @return the first derivative of this polynomial
118 * @param x
119 */
120 Real evaluateDerivative(const Real& x) {
121 Real result = Real();
122 ExponentType exponent;
123 CoefficientType coefficient;
124
125 for (iterator i = polyPairMap_.begin(); i != polyPairMap_.end(); ++i) {
126 exponent = i->first;
127 coefficient = i->second;
128 result += fastpow(x, exponent - 1) * coefficient * exponent;
129 }
130
131 return result;
132 }
133
134 /**
135 * Set the coefficent of the specified exponent, if the
136 * coefficient is already there, it will be overwritten.
137 * @param exponent exponent of a term in this Polynomial
138 * @param coefficient multiplier of a term in this Polynomial
139 */
140 void setCoefficient(int exponent, const Real& coefficient) {
141 polyPairMap_[exponent] = coefficient;
142 }
143
144 /**
145 * Set the coefficent of the specified exponent. If the
146 * coefficient is already there, just add the new coefficient to
147 * the old one, otherwise, just call setCoefficent
148 * @param exponent exponent of a term in this Polynomial
149 * @param coefficient multiplier of a term in this Polynomial
150 */
151 void addCoefficient(int exponent, const Real& coefficient) {
152 iterator i = polyPairMap_.find(exponent);
153
154 if (i != end()) {
155 i->second += coefficient;
156 } else {
157 setCoefficient(exponent, coefficient);
158 }
159 }
160
161 /**
162 * Returns the coefficient associated with the given power for
163 * this Polynomial.
164 * @return the coefficient associated with the given power for
165 * this Polynomial
166 * @param exponent exponent of any term in this Polynomial
167 */
168 Real getCoefficient(ExponentType exponent) {
169 iterator i = polyPairMap_.find(exponent);
170
171 if (i != end()) {
172 return i->second;
173 } else {
174 return Real(0);
175 }
176 }
177
178 iterator begin() { return polyPairMap_.begin(); }
179 const_iterator begin() const { return polyPairMap_.begin(); }
180
181 iterator end() { return polyPairMap_.end(); }
182 const_iterator end() const { return polyPairMap_.end(); }
183
184 iterator find(ExponentType exponent) { return polyPairMap_.find(exponent); }
185
186 size_t size() { return polyPairMap_.size(); }
187
188 int degree() {
189 int deg = 0;
190 for (iterator i = polyPairMap_.begin(); i != polyPairMap_.end(); ++i) {
191 if (i->first > deg) deg = i->first;
192 }
193 return deg;
194 }
195
196 PolynomialType& operator+=(const PolynomialType& p) {
197 typename Polynomial<Real>::const_iterator i;
198
199 for (i = p.begin(); i != p.end(); ++i) {
200 this->addCoefficient(i->first, i->second);
201 }
202
203 return *this;
204 }
205
206 PolynomialType& operator-=(const PolynomialType& p) {
207 typename Polynomial<Real>::const_iterator i;
208 for (i = p.begin(); i != p.end(); ++i) {
209 this->addCoefficient(i->first, -i->second);
210 }
211 return *this;
212 }
213
214 PolynomialType& operator*=(const PolynomialType& p) {
215 typename Polynomial<Real>::const_iterator i;
216 typename Polynomial<Real>::const_iterator j;
217 Polynomial<Real> p2(*this);
218
219 polyPairMap_.clear(); // clear out old map
220 for (i = p2.begin(); i != p2.end(); ++i) {
221 for (j = p.begin(); j != p.end(); ++j) {
222 this->addCoefficient(i->first + j->first, i->second * j->second);
223 }
224 }
225 return *this;
226 }
227
228 PolynomialType& operator*=(const Real v) {
229 typename Polynomial<Real>::const_iterator i;
230 // Polynomial<Real> result;
231
232 for (i = this->begin(); i != this->end(); ++i) {
233 this->setCoefficient(i->first, i->second * v);
234 }
235
236 return *this;
237 }
238
239 PolynomialType& operator+=(const Real v) {
240 this->addCoefficient(0, v);
241 return *this;
242 }
243
244 /**
245 * Returns the first derivative of this polynomial.
246 * @return the first derivative of this polynomial
247 */
248 PolynomialType* getDerivative() {
249 Polynomial<Real>* p = new Polynomial<Real>();
250
251 typename Polynomial<Real>::const_iterator i;
252 ExponentType exponent;
253 CoefficientType coefficient;
254
255 for (i = this->begin(); i != this->end(); ++i) {
256 exponent = i->first;
257 coefficient = i->second;
258 p->setCoefficient(exponent - 1, coefficient * exponent);
259 }
260
261 return p;
262 }
263
264 // Creates the Companion matrix for a given polynomial
265 DynamicRectMatrix<Real> CreateCompanion() {
266 int rank = degree();
267 DynamicRectMatrix<Real> mat(rank, rank);
268 Real majorCoeff = getCoefficient(rank);
269 for (int i = 0; i < rank; ++i) {
270 for (int j = 0; j < rank; ++j) {
271 if (i - j == 1) {
272 mat(i, j) = 1;
273 } else if (j == rank - 1) {
274 mat(i, j) = -1 * getCoefficient(i) / majorCoeff;
275 }
276 }
277 }
278 return mat;
279 }
280
281 // Find the Roots of a given polynomial
282 std::vector<complex<Real>> FindRoots() {
283 int rank = degree();
284 DynamicRectMatrix<Real> companion = CreateCompanion();
285 JAMA::Eigenvalue<Real> eig(companion);
286 DynamicVector<Real> reals, imags;
287 eig.getRealEigenvalues(reals);
288 eig.getImagEigenvalues(imags);
289
290 std::vector<complex<Real>> roots;
291 for (int i = 0; i < rank; i++) {
292 roots.push_back(complex<Real>(reals(i), imags(i)));
293 }
294
295 return roots;
296 }
297
298 std::vector<Real> FindRealRoots() {
299 const Real fEpsilon = 1.0e-8;
300 std::vector<Real> roots;
301 roots.clear();
302
303 const int deg = degree();
304
305 switch (deg) {
306 case 1: {
307 Real fC1 = getCoefficient(1);
308 Real fC0 = getCoefficient(0);
309 roots.push_back(-fC0 / fC1);
310 return roots;
311 }
312 case 2: {
313 Real fC2 = getCoefficient(2);
314 Real fC1 = getCoefficient(1);
315 Real fC0 = getCoefficient(0);
316 Real fDiscr = fC1 * fC1 - 4.0 * fC0 * fC2;
317 if (abs(fDiscr) <= fEpsilon) { fDiscr = (Real)0.0; }
318
319 if (fDiscr < (Real)0.0) { // complex roots only
320 return roots;
321 }
322
323 Real fTmp = ((Real)0.5) / fC2;
324
325 if (fDiscr > (Real)0.0) { // 2 real roots
326 fDiscr = sqrt(fDiscr);
327 roots.push_back(fTmp * (-fC1 - fDiscr));
328 roots.push_back(fTmp * (-fC1 + fDiscr));
329 } else {
330 roots.push_back(-fTmp * fC1); // 1 real root
331 }
332 }
333 return roots;
334 case 3: {
335 Real fC3 = getCoefficient(3);
336 Real fC2 = getCoefficient(2);
337 Real fC1 = getCoefficient(1);
338 Real fC0 = getCoefficient(0);
339
340 // make polynomial monic, x^3+c2*x^2+c1*x+c0
341 Real fInvC3 = ((Real)1.0) / fC3;
342 fC0 *= fInvC3;
343 fC1 *= fInvC3;
344 fC2 *= fInvC3;
345
346 // convert to y^3+a*y+b = 0 by x = y-c2/3
347 const Real fThird = (Real)1.0 / (Real)3.0;
348 const Real fTwentySeventh = (Real)1.0 / (Real)27.0;
349 Real fOffset = fThird * fC2;
350 Real fA = fC1 - fC2 * fOffset;
351 Real fB = fC0 + fC2 * (((Real)2.0) * fC2 * fC2 - ((Real)9.0) * fC1) *
352 fTwentySeventh;
353 Real fHalfB = ((Real)0.5) * fB;
354
355 Real fDiscr = fHalfB * fHalfB + fA * fA * fA * fTwentySeventh;
356 if (fabs(fDiscr) <= fEpsilon) { fDiscr = (Real)0.0; }
357
358 if (fDiscr > (Real)0.0) { // 1 real, 2 complex roots
359
360 fDiscr = sqrt(fDiscr);
361 Real fTemp = -fHalfB + fDiscr;
362 Real root;
363 if (fTemp >= (Real)0.0) {
364 root = pow(fTemp, fThird);
365 } else {
366 root = -pow(-fTemp, fThird);
367 }
368 fTemp = -fHalfB - fDiscr;
369 if (fTemp >= (Real)0.0) {
370 root += pow(fTemp, fThird);
371 } else {
372 root -= pow(-fTemp, fThird);
373 }
374 root -= fOffset;
375
376 roots.push_back(root);
377 } else if (fDiscr < (Real)0.0) {
378 const Real fSqrt3 = sqrt((Real)3.0);
379 Real fDist = sqrt(-fThird * fA);
380 Real fAngle = fThird * atan2(sqrt(-fDiscr), -fHalfB);
381 Real fCos = cos(fAngle);
382 Real fSin = sin(fAngle);
383 roots.push_back(((Real)2.0) * fDist * fCos - fOffset);
384 roots.push_back(-fDist * (fCos + fSqrt3 * fSin) - fOffset);
385 roots.push_back(-fDist * (fCos - fSqrt3 * fSin) - fOffset);
386 } else {
387 Real fTemp;
388 if (fHalfB >= (Real)0.0) {
389 fTemp = -pow(fHalfB, fThird);
390 } else {
391 fTemp = pow(-fHalfB, fThird);
392 }
393 roots.push_back(((Real)2.0) * fTemp - fOffset);
394 roots.push_back(-fTemp - fOffset);
395 roots.push_back(-fTemp - fOffset);
396 }
397 }
398 return roots;
399 case 4: {
400 Real fC4 = getCoefficient(4);
401 Real fC3 = getCoefficient(3);
402 Real fC2 = getCoefficient(2);
403 Real fC1 = getCoefficient(1);
404 Real fC0 = getCoefficient(0);
405
406 // make polynomial monic, x^4+c3*x^3+c2*x^2+c1*x+c0
407 Real fInvC4 = ((Real)1.0) / fC4;
408 fC0 *= fInvC4;
409 fC1 *= fInvC4;
410 fC2 *= fInvC4;
411 fC3 *= fInvC4;
412
413 // reduction to resolvent cubic polynomial y^3+r2*y^2+r1*y+r0 = 0
414 Real fR0 = -fC3 * fC3 * fC0 + ((Real)4.0) * fC2 * fC0 - fC1 * fC1;
415 Real fR1 = fC3 * fC1 - ((Real)4.0) * fC0;
416 Real fR2 = -fC2;
417 Polynomial<Real> tempCubic;
418 tempCubic.setCoefficient(0, fR0);
419 tempCubic.setCoefficient(1, fR1);
420 tempCubic.setCoefficient(2, fR2);
421 tempCubic.setCoefficient(3, 1.0);
422 std::vector<Real> cubeRoots = tempCubic.FindRealRoots(); // always
423 // produces
424 // at
425 // least
426 // one
427 // root
428 Real fY = cubeRoots[0];
429
430 Real fDiscr = ((Real)0.25) * fC3 * fC3 - fC2 + fY;
431 if (fabs(fDiscr) <= fEpsilon) { fDiscr = (Real)0.0; }
432
433 if (fDiscr > (Real)0.0) {
434 Real fR = sqrt(fDiscr);
435 Real fT1 = ((Real)0.75) * fC3 * fC3 - fR * fR - ((Real)2.0) * fC2;
436 Real fT2 =
437 (((Real)4.0) * fC3 * fC2 - ((Real)8.0) * fC1 - fC3 * fC3 * fC3) /
438 (((Real)4.0) * fR);
439
440 Real fTplus = fT1 + fT2;
441 Real fTminus = fT1 - fT2;
442 if (fabs(fTplus) <= fEpsilon) { fTplus = (Real)0.0; }
443 if (fabs(fTminus) <= fEpsilon) { fTminus = (Real)0.0; }
444
445 if (fTplus >= (Real)0.0) {
446 Real fD = sqrt(fTplus);
447 roots.push_back(-((Real)0.25) * fC3 + ((Real)0.5) * (fR + fD));
448 roots.push_back(-((Real)0.25) * fC3 + ((Real)0.5) * (fR - fD));
449 }
450 if (fTminus >= (Real)0.0) {
451 Real fE = sqrt(fTminus);
452 roots.push_back(-((Real)0.25) * fC3 + ((Real)0.5) * (fE - fR));
453 roots.push_back(-((Real)0.25) * fC3 - ((Real)0.5) * (fE + fR));
454 }
455 } else if (fDiscr < (Real)0.0) {
456 // roots.clear();
457 } else {
458 Real fT2 = fY * fY - ((Real)4.0) * fC0;
459 if (fT2 >= -fEpsilon) {
460 if (fT2 < (Real)0.0) { // round to zero
461 fT2 = (Real)0.0;
462 }
463 fT2 = ((Real)2.0) * sqrt(fT2);
464 Real fT1 = ((Real)0.75) * fC3 * fC3 - ((Real)2.0) * fC2;
465 if (fT1 + fT2 >= fEpsilon) {
466 Real fD = sqrt(fT1 + fT2);
467 roots.push_back(-((Real)0.25) * fC3 + ((Real)0.5) * fD);
468 roots.push_back(-((Real)0.25) * fC3 - ((Real)0.5) * fD);
469 }
470 if (fT1 - fT2 >= fEpsilon) {
471 Real fE = sqrt(fT1 - fT2);
472 roots.push_back(-((Real)0.25) * fC3 + ((Real)0.5) * fE);
473 roots.push_back(-((Real)0.25) * fC3 - ((Real)0.5) * fE);
474 }
475 }
476 }
477 }
478 return roots;
479 default: {
480 DynamicRectMatrix<Real> companion = CreateCompanion();
481 JAMA::Eigenvalue<Real> eig(companion);
482 DynamicVector<Real> reals, imags;
483 eig.getRealEigenvalues(reals);
484 eig.getImagEigenvalues(imags);
485
486 for (int i = 0; i < deg; i++) {
487 if (fabs(imags(i)) < fEpsilon) roots.push_back(reals(i));
488 }
489 }
490 return roots;
491 }
492 }
493
494 private:
495 PolynomialPairMap polyPairMap_;
496 };
497
498 /**
499 * Generates and returns the product of two given Polynomials.
500 * @return A Polynomial containing the product of the two given Polynomial
501 * parameters
502 */
503 template<typename Real>
504 Polynomial<Real> operator*(const Polynomial<Real>& p1,
505 const Polynomial<Real>& p2) {
506 typename Polynomial<Real>::const_iterator i;
507 typename Polynomial<Real>::const_iterator j;
508 Polynomial<Real> p;
509
510 for (i = p1.begin(); i != p1.end(); ++i) {
511 for (j = p2.begin(); j != p2.end(); ++j) {
512 p.addCoefficient(i->first + j->first, i->second * j->second);
513 }
514 }
515
516 return p;
517 }
518
519 template<typename Real>
520 Polynomial<Real> operator*(const Polynomial<Real>& p, const Real v) {
521 typename Polynomial<Real>::const_iterator i;
522 Polynomial<Real> result;
523
524 for (i = p.begin(); i != p.end(); ++i) {
525 result.setCoefficient(i->first, i->second * v);
526 }
527
528 return result;
529 }
530
531 template<typename Real>
532 Polynomial<Real> operator*(const Real v, const Polynomial<Real>& p) {
533 typename Polynomial<Real>::const_iterator i;
534 Polynomial<Real> result;
535
536 for (i = p.begin(); i != p.end(); ++i) {
537 result.setCoefficient(i->first, i->second * v);
538 }
539
540 return result;
541 }
542
543 /**
544 * Generates and returns the sum of two given Polynomials.
545 * @param p1 the first polynomial
546 * @param p2 the second polynomial
547 */
548 template<typename Real>
549 Polynomial<Real> operator+(const Polynomial<Real>& p1,
550 const Polynomial<Real>& p2) {
551 Polynomial<Real> p(p1);
552
553 typename Polynomial<Real>::const_iterator i;
554
555 for (i = p2.begin(); i != p2.end(); ++i) {
556 p.addCoefficient(i->first, i->second);
557 }
558
559 return p;
560 }
561
562 /**
563 * Generates and returns the difference of two given Polynomials.
564 * @return
565 * @param p1 the first polynomial
566 * @param p2 the second polynomial
567 */
568 template<typename Real>
569 Polynomial<Real> operator-(const Polynomial<Real>& p1,
570 const Polynomial<Real>& p2) {
571 Polynomial<Real> p(p1);
572
573 typename Polynomial<Real>::const_iterator i;
574
575 for (i = p2.begin(); i != p2.end(); ++i) {
576 p.addCoefficient(i->first, -i->second);
577 }
578
579 return p;
580 }
581
582 /**
583 * Returns the first derivative of this polynomial.
584 * @return the first derivative of this polynomial
585 */
586 template<typename Real>
587 Polynomial<Real>* getDerivative(const Polynomial<Real>& p1) {
588 Polynomial<Real>* p = new Polynomial<Real>();
589
590 typename Polynomial<Real>::const_iterator i;
591 int exponent;
592 Real coefficient;
593
594 for (i = p1.begin(); i != p1.end(); ++i) {
595 exponent = i->first;
596 coefficient = i->second;
597 p->setCoefficient(exponent - 1, coefficient * exponent);
598 }
599
600 return p;
601 }
602
603 /**
604 * Tests if two polynomial have the same exponents
605 * @return true if all of the exponents in these Polynomial are identical
606 * @param p1 the first polynomial
607 * @param p2 the second polynomial
608 * @note this function does not compare the coefficient
609 */
610 template<typename Real>
611 bool equal(const Polynomial<Real>& p1, const Polynomial<Real>& p2) {
612 typename Polynomial<Real>::const_iterator i;
613 typename Polynomial<Real>::const_iterator j;
614
615 if (p1.size() != p2.size()) { return false; }
616
617 for (i = p1.begin(), j = p2.begin(); i != p1.end() && j != p2.end();
618 ++i, ++j) {
619 if (i->first != j->first) { return false; }
620 }
621
622 return true;
623 }
624
625 using DoublePolynomial = Polynomial<RealType>;
626} // namespace OpenMD
627
628#endif // MATH_POLYNOMIAL_HPP
Computes eigenvalues and eigenvectors of a real (non-complex) matrix.
A generic Polynomial class.
void addCoefficient(int exponent, const Real &coefficient)
Set the coefficent of the specified exponent.
Real evaluateDerivative(const Real &x)
Returns the first derivative of this polynomial.
Real getCoefficient(ExponentType exponent)
Returns the coefficient associated with the given power for this Polynomial.
Real evaluate(const Real &x)
Calculates the value of this Polynomial evaluated at the given x value.
PolynomialType * getDerivative()
Returns the first derivative of this polynomial.
void setCoefficient(int exponent, const Real &coefficient)
Set the coefficent of the specified exponent, if the coefficient is already there,...
This basic Periodic Table class was originally taken from the data.cpp file in OpenBabel.
DynamicRectMatrix< Real > operator-(const DynamicRectMatrix< Real > &m)
Negate the value of every element of this matrix.
bool equal(const Polynomial< Real > &p1, const Polynomial< Real > &p2)
Tests if two polynomial have the same exponents.
Polynomial< Real > * getDerivative(const Polynomial< Real > &p1)
Returns the first derivative of this polynomial.
DynamicRectMatrix< Real > operator*(const DynamicRectMatrix< Real > &m, Real s)
Return the multiplication of scalar and matrix (m * s).