diff --git a/src/mantissa/LICENSE-2.0.txt b/src/mantissa/LICENSE-2.0.txt new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/src/mantissa/LICENSE-2.0.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/src/mantissa/NOTICE.txt b/src/mantissa/NOTICE.txt new file mode 100644 index 000000000..0a60030d4 --- /dev/null +++ b/src/mantissa/NOTICE.txt @@ -0,0 +1,103 @@ +Apache commons-math +Copyright 2006 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). + + + +This product includes software developed by +Luc Maisonobe and licensed to the Apache Software Foundation. + + + +This product includes software translated from the lmder, lmpar +and qrsolv Fortran routines from the Minpack package and +distributed under the following disclaimer: + +---------- http://www.netlib.org/minpack/disclaimer ---------- +Minpack Copyright Notice (1999) University of Chicago. All rights reserved + +Redistribution and use in source and binary forms, with or +without modification, are permitted provided that the +following conditions are met: + +1. Redistributions of source code must retain the above +copyright notice, this list of conditions and the following +disclaimer. + +2. Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following +disclaimer in the documentation and/or other materials +provided with the distribution. + +3. The end-user documentation included with the +redistribution, if any, must include the following +acknowledgment: + + "This product includes software developed by the + University of Chicago, as Operator of Argonne National + Laboratory. + +Alternately, this acknowledgment may appear in the software +itself, if and wherever such third-party acknowledgments +normally appear. + +4. WARRANTY DISCLAIMER. THE SOFTWARE IS SUPPLIED "AS IS" +WITHOUT WARRANTY OF ANY KIND. THE COPYRIGHT HOLDER, THE +UNITED STATES, THE UNITED STATES DEPARTMENT OF ENERGY, AND +THEIR EMPLOYEES: (1) DISCLAIM ANY WARRANTIES, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO ANY IMPLIED WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE +OR NON-INFRINGEMENT, (2) DO NOT ASSUME ANY LEGAL LIABILITY +OR RESPONSIBILITY FOR THE ACCURACY, COMPLETENESS, OR +USEFULNESS OF THE SOFTWARE, (3) DO NOT REPRESENT THAT USE OF +THE SOFTWARE WOULD NOT INFRINGE PRIVATELY OWNED RIGHTS, (4) +DO NOT WARRANT THAT THE SOFTWARE WILL FUNCTION +UNINTERRUPTED, THAT IT IS ERROR-FREE OR THAT ANY ERRORS WILL +BE CORRECTED. + +5. LIMITATION OF LIABILITY. IN NO EVENT WILL THE COPYRIGHT +HOLDER, THE UNITED STATES, THE UNITED STATES DEPARTMENT OF +ENERGY, OR THEIR EMPLOYEES: BE LIABLE FOR ANY INDIRECT, +INCIDENTAL, CONSEQUENTIAL, SPECIAL OR PUNITIVE DAMAGES OF +ANY KIND OR NATURE, INCLUDING BUT NOT LIMITED TO LOSS OF +PROFITS OR LOSS OF DATA, FOR ANY REASON WHATSOEVER, WHETHER +SUCH LIABILITY IS ASSERTED ON THE BASIS OF CONTRACT, TORT +(INCLUDING NEGLIGENCE OR STRICT LIABILITY), OR OTHERWISE, +EVEN IF ANY OF SAID PARTIES HAS BEEN WARNED OF THE +POSSIBILITY OF SUCH LOSS OR DAMAGES. +---------- http://www.netlib.org/minpack/disclaimer ---------- + + + +This product includes software translated from the odex Fortran routine +developed by E. Hairer and G. Wanner and distributed under the following +license: + +---------- http://www.unige.ch/~hairer/prog/licence.txt ---------- +Copyright (c) 2004, Ernst Hairer + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +- Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +---------- http://www.unige.ch/~hairer/prog/licence.txt ---------- diff --git a/src/mantissa/src/org/spaceroots/mantissa/MantissaException.java b/src/mantissa/src/org/spaceroots/mantissa/MantissaException.java new file mode 100644 index 000000000..59c853d72 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/MantissaException.java @@ -0,0 +1,119 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa; + +import java.text.MessageFormat; +import java.util.ResourceBundle; +import java.util.MissingResourceException; + +/** This class is the base class for all specific exceptions thrown by + * the mantissa classes. + + *

When the mantissa classes throw exceptions that are specific to + * the package, these exceptions are always subclasses of + * MantissaException. When exceptions that are already covered by the + * standard java API should be thrown, like + * ArrayIndexOutOfBoundsException or IllegalArgumentException, these + * standard exceptions are thrown rather than the mantissa specific + * ones.

+ + * @version $Id: MantissaException.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class MantissaException + extends Exception { + + private static final long serialVersionUID = 1L; + private static ResourceBundle resources + = ResourceBundle.getBundle("org.spaceroots.mantissa.MessagesResources"); + + /** Translate a string. + * @param s string to translate + * @return translated string + */ + public static String translate(String s) { + try { + return resources.getString(s); + } catch (MissingResourceException mre) { + return s; + } + } + + /** Translate a message. + * @param specifier format specifier (to be translated) + * @param parts to insert in the format (no translation) + * @return translated message + */ + public static String translate(String specifier, String[] parts) { + return new MessageFormat(translate(specifier)).format(parts); + } + + /** Simple constructor. + * Build an exception with an empty message + */ + public MantissaException() { + super(); + } + + /** Simple constructor. + * Build an exception by translating the specified message + * @param message message to translate + */ + public MantissaException(String message) { + super(translate(message)); + } + + /** Simple constructor. + * Build an exception by translating and formating a message + * @param specifier format specifier (to be translated) + * @param parts to insert in the format (no translation) + */ + public MantissaException(String specifier, String[] parts) { + super(translate(specifier, parts)); + } + + /** Simple constructor. + * Build an exception from a cause + * @param cause cause of this exception + */ + public MantissaException(Throwable cause) { + super(cause); + } + + /** Simple constructor. + * Build an exception from a message and a cause + * @param message message to translate + * @param cause cause of this exception + */ + public MantissaException(String message, Throwable cause) { + super(translate(message), cause); + } + + /** Simple constructor. + * Build an exception from a message and a cause + * @param specifier format specifier (to be translated) + * @param parts to insert in the format (no translation) + * @param cause cause of this exception + */ + public MantissaException(String specifier, String[] parts, Throwable cause) { + super(translate(specifier, parts), cause); + } + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/MessagesResources.java b/src/mantissa/src/org/spaceroots/mantissa/MessagesResources.java new file mode 100644 index 000000000..fbf1b28fa --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/MessagesResources.java @@ -0,0 +1,113 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa; + +import java.util.ListResourceBundle; + +/** This class gather the message resources for the mantissa library. + * @version $Id: MessagesResources.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + */ + +public class MessagesResources + extends ListResourceBundle { + + /** Simple constructor. + */ + public MessagesResources() { + } + + public Object[][] getContents() { + return contents; + } + + static final Object[][] contents = { + + // org.spaceroots.mantissa.estimation.GaussNewtonEstimator + { "unable to converge in {0} iterations", + "unable to converge in {0} iterations" }, + + // org.spaceroots.mantissa.estimation.LevenbergMarquardtEstimator + { "cost relative tolerance is too small ({0}), no further reduction in the sum of squares is possible", + "cost relative tolerance is too small ({0}), no further reduction in the sum of squares is possible" }, + { "parameters relative tolerance is too small ({0}), no further improvement in the approximate solution is possible", + "parameters relative tolerance is too small ({0}), no further improvement in the approximate solution is possible" }, + { "orthogonality tolerance is too small ({0}), solution is orthogonal to the jacobian", + "orthogonality tolerance is too small ({0}), solution is orthogonal to the jacobian" }, + { "maximal number of evaluations exceeded ({0})", + "maximal number of evaluations exceeded ({0})" }, + + // org.spaceroots.mantissa.fitting.HarmonicCoefficientsGuesser + { "unable to guess a first estimate", + "unable to guess a first estimate" }, + + // org.spaceroots.mantissa.fitting.HarmonicFitter + { "sample must contain at least {0} points", + "sample must contain at least {0} points" }, + + // org.spaceroots.mantissa.functions.ExhaustedSampleException + { "sample contains only {0} elements", + "sample contains only {0} elements" }, + + // org.spaceroots.mantissa.geometry.CardanEulerSingularityException + { "Cardan angles singularity", + "Cardan angles singularity" }, + { "Euler angles singularity", + "Euler angles singularity" }, + + // org.spaceroots.mantissa.geometry.Rotation + { "a {0}x{1} matrix cannot be a rotation matrix", + "a {0}x{1} matrix cannot be a rotation matrix" }, + { "the closest orthogonal matrix has a negative determinant {0}", + "the closest orthogonal matrix has a negative determinant {0}" }, + { "unable to orthogonalize matrix in {0} iterations", + "unable to orthogonalize matrix in {0} iterations" }, + + // org.spaceroots.mantissa.linalg;.SingularMatrixException + { "singular matrix", + "singular matrix" }, + + // org.spaceroots.mantissa.ode.AdaptiveStepsizeIntegrator + { "minimal step size ({0}) reached, integration needs {1}", + "minimal step size ({0}) reached, integration needs {1}" }, + + // org.spaceroots.mantissa.ode.GraggBulirschStoerIntegrator, + // org.spaceroots.mantissa.ode.RungeKuttaFehlbergIntegrator, + // org.spaceroots.mantissa.ode.RungeKuttaIntegrator + { "dimensions mismatch: ODE problem has dimension {0}," + + " state vector has dimension {1}", + "dimensions mismatch: ODE problem has dimension {0}," + + " state vector has dimension {1}" }, + { "too small integration interval: length = {0}", + "too small integration interval: length = {0}" }, + + // org.spaceroots.mantissa.optimization.DirectSearchOptimizer + { "none of the {0} start points lead to convergence", + "none of the {0} start points lead to convergence" }, + + // org.spaceroots.mantissa.random.CorrelatedRandomVectorGenerator + { "dimension mismatch {0} != {1}", + "dimension mismatch {0} != {1}" }, + + // org.spaceroots.mantissa.random.NotPositiveDefiniteMatrixException + { "not positive definite matrix", + "not positive definite matrix" } + + }; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/MessagesResources_fr.java b/src/mantissa/src/org/spaceroots/mantissa/MessagesResources_fr.java new file mode 100644 index 000000000..cd166354d --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/MessagesResources_fr.java @@ -0,0 +1,112 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa; + +import java.util.ListResourceBundle; + +/** This class gather the message resources for the mantissa library. + * @version $Id: MessagesResources_fr.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + */ +public class MessagesResources_fr + extends ListResourceBundle { + + /** Simple constructor. + */ + public MessagesResources_fr() { + } + + public Object[][] getContents() { + return contents; + } + + static final Object[][] contents = { + + // org.spaceroots.mantissa.estimation.GaussNewtonEstimator + { "unable to converge in {0} iterations", + "pas de convergence apr\u00e8s {0} it\u00e9rations" }, + + // org.spaceroots.mantissa.estimation.LevenbergMarquardtEstimator + { "cost relative tolerance is too small ({0}), no further reduction in the sum of squares is possible", + "trop petite tol\u00e9rance relative sur le co\u00fbt ({0}), aucune r\u00e9duction de la somme des carr\u00e9s n''est possible" }, + { "parameters relative tolerance is too small ({0}), no further improvement in the approximate solution is possible", + "trop petite tol\u00e9rance relative sur les param\u00e8tres ({0}), aucune am\u00e9lioration de la solution approximative n''est possible" }, + { "orthogonality tolerance is too small ({0}), solution is orthogonal to the jacobian", + "trop petite tol\u00e9rance sur l''orthogonalit\u00e9 ({0}), la solution est orthogonale \u00e0 la jacobienne" }, + { "maximal number of evaluations exceeded ({0})", + "nombre maximal d''\u00e9valuations d\u00e9pass\u00e9 ({0})" }, + + // org.spaceroots.mantissa.fitting.HarmonicCoefficientsGuesser + { "unable to guess a first estimate", + "impossible de trouver une premi\u00e8re estim\u00e9e" }, + + // org.spaceroots.mantissa.fitting.HarmonicFitter + { "sample must contain at least {0} points", + "l''\u00e9chantillon doit contenir au moins {0} points" }, + + // org.spaceroots.mantissa.functions.ExhaustedSampleException + { "sample contains only {0} elements", + "l''\u00e9chantillon ne contient que {0} points" }, + + // org.spaceroots.mantissa.geometry.CardanEulerSingularityException + { "Cardan angles singularity", + "singularit\u00e9 d''angles de Cardan" }, + { "Euler angles singularity", + "singularit\u00e9 d''angles d''Euler" }, + + // org.spaceroots.mantissa.geometry.Rotation + { "a {0}x{1} matrix cannot be a rotation matrix", + "une matrice {0}x{1} ne peut pas \u00e9tre une matrice de rotation" }, + { "the closest orthogonal matrix has a negative determinant {0}", + "la matrice orthogonale la plus proche a un d\u00e9terminant n\u00e9gatif {0}" }, + { "unable to orthogonalize matrix in {0} iterations", + "impossible de rendre la matrice orthogonale en {0} it\u00e9rations" }, + + // org.spaceroots.mantissa.linalg;.SingularMatrixException + { "singular matrix", + "matrice singuli\u00e8re" }, + + // org.spaceroots.mantissa.ode.AdaptiveStepsizeIntegrator + { "minimal step size ({0}) reached, integration needs {1}", + "pas minimal ({0}) atteint, l''int\u00e9gration n\u00e9cessite {1}" }, + + // org.spaceroots.mantissa.ode.GraggBulirschStoerIntegrator, + // org.spaceroots.mantissa.ode.RungeKuttaFehlbergIntegrator, + // org.spaceroots.mantissa.ode.RungeKuttaIntegrator + { "dimensions mismatch: ODE problem has dimension {0}," + + " state vector has dimension {1}", + "incompatibilit\u00e9 de dimensions entre le probl\u00e8me ODE ({0})," + + " et le vecteur d'\u00e9tat ({1})" }, + { "too small integration interval: length = {0}", + "intervalle d''int\u00e9gration trop petit : {0}" }, + + // org.spaceroots.mantissa.optimization.DirectSearchOptimizer + { "none of the {0} start points lead to convergence", + "aucun des {0} points de d\u00e9part n''aboutit \u00e0 une convergence" }, + + // org.spaceroots.mantissa.random.CorrelatedRandomVectorGenerator + { "dimension mismatch {0} != {1}", + "dimensions incompatibles {0} != {1}" }, + + // org.spaceroots.mantissa.random.NotPositiveDefiniteMatrixException + { "not positive definite matrix", + "matrice non d\u00e9finie positive" } + + }; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/algebra/Chebyshev.java b/src/mantissa/src/org/spaceroots/mantissa/algebra/Chebyshev.java new file mode 100755 index 000000000..47e828df3 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/algebra/Chebyshev.java @@ -0,0 +1,105 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.algebra; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class implements Chebyshev polynomials. + + *

Chebyshev polynomials can be defined by the following recurrence + * relations: + *

+ *  T0(X)   = 1
+ *  T1(X)   = X
+ *  Tk+1(X) = 2X Tk(X) - Tk-1(X)
+ * 

+ + * @version $Id: Chebyshev.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class Chebyshev + extends OrthogonalPolynomial { + + /** Simple constructor. + * Build a degree 0 Chebyshev polynomial + */ + public Chebyshev() { + super(0, l, maxDegree); + } + + /** Simple constructor. + * Build a degree d Chebyshev polynomial + * @param d degree of the polynomial + */ + public Chebyshev(int d) { + super(d, l, maxDegree); + } + + /** Initialize the recurrence coefficients. + * The recurrence relation is + *
Tk+1(X) = 2X Tk(X) - Tk-1(X)
+ * @param k index of the current step + * @param b2k coefficient to initialize (b2k = a2k / a1k) + * @param b3k coefficient to initialize (b3k = a3k / a1k) + * @param b4k coefficient to initialize (b4k = a4k / a1k) + */ + protected void initRecurrenceCoefficients(int k, + RationalNumber b2k, + RationalNumber b3k, + RationalNumber b4k) { + b2k.reset(0l); + b3k.reset(2l); + b4k.reset(1l); + } + + /** Set the maximal degree of already computed polynomials. + * @param d maximal degree of already computed polynomials + */ + protected void setMaxDegree(int d) { + maxDegree = d; + } + + private static final long serialVersionUID = 8367010179599693222L; + + /** List holding the coefficients of the polynomials computed so far. */ + private static List l; + + /** Maximal degree of the polynomials computed so far. */ + private static int maxDegree; + + /** Build the first two polynomials. */ + static { + + l = new ArrayList (); + + // T0(X) = 1 + l.add(new RationalNumber(1l)); + + // T1(X) = X + l.add(new RationalNumber(0l)); + l.add(new RationalNumber(1l)); + + maxDegree = 1; + + } + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/algebra/Hermite.java b/src/mantissa/src/org/spaceroots/mantissa/algebra/Hermite.java new file mode 100755 index 000000000..4a7d9d599 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/algebra/Hermite.java @@ -0,0 +1,104 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.algebra; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class implements Hermite polynomials. + + *

Hermite polynomials can be defined by the following recurrence + * relations: + *

+ *  H0(X)   = 1
+ *  H1(X)   = 2X
+ *  Hk+1(X) = 2X Hk(X) - 2k Hk-1(X)
+ * 

+ + * @version $Id: Hermite.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ +public class Hermite + extends OrthogonalPolynomial { + + /** Simple constructor. + * Build a degree 0 Hermite polynomial + */ + public Hermite() { + super(0, l, maxDegree); + } + + /** Simple constructor. + * Build a degree d Hermite polynomial + * @param d degree of the polynomial + */ + public Hermite(int d) { + super(d, l, maxDegree); + } + + /** Initialize the recurrence coefficients. + * The recurrence relation is + *
Hk+1(X) = 2X Hk(X) - 2k Hk-1(X)
+ * @param k index of the current step + * @param b2k coefficient to initialize (b2k = a2k / a1k) + * @param b3k coefficient to initialize (b3k = a3k / a1k) + * @param b4k coefficient to initialize (b4k = a4k / a1k) + */ + protected void initRecurrenceCoefficients(int k, + RationalNumber b2k, + RationalNumber b3k, + RationalNumber b4k) { + b2k.reset(0l); + b3k.reset(2l); + b4k.reset(2l * k); + } + + /** Set the maximal degree of already computed polynomials. + * @param d maximal degree of already computed polynomials + */ + protected void setMaxDegree(int d) { + maxDegree = d; + } + + private static final long serialVersionUID = -4639726453485128770L; + + /** Table holding the coefficients of the polynomials computed so far. */ + private static List l; + + /** Maximal degree of the polynomials computed so far. */ + private static int maxDegree; + + /** Build the first two polynomials. */ + static { + + l = new ArrayList (); + + // H0(X) = 1 + l.add(new RationalNumber(1l)); + + // H1(X) = 2X + l.add(new RationalNumber(0l)); + l.add(new RationalNumber(2l)); + + maxDegree = 1; + + } + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/algebra/Laguerre.java b/src/mantissa/src/org/spaceroots/mantissa/algebra/Laguerre.java new file mode 100644 index 000000000..8f87ebdd0 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/algebra/Laguerre.java @@ -0,0 +1,105 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.algebra; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class implements Laguerre polynomials. + + *

Laguerre polynomials can be defined by the following recurrence + * relations: + *

+ *        L0(X)   = 1
+ *        L1(X)   = 1 - X
+ *  (k+1) Lk+1(X) = (2k + 1 - X) Lk(X) - k Lk-1(X)
+ * 

+ + * @version $Id: Laguerre.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ +public class Laguerre + extends OrthogonalPolynomial { + + /** Simple constructor. + * Build a degree 0 Laguerre polynomial + */ + public Laguerre() { + super(0, l, maxDegree); + } + + /** Simple constructor. + * Build a degree d Laguerre polynomial + * @param d degree of the polynomial + */ + public Laguerre(int d) { + super(d, l, maxDegree); + } + + /** Initialize the recurrence coefficients. + * The recurrence relation is + *
(k+1) Lk+1(X) = (2k + 1 - X) Lk(X) - k Lk-1(X)
+ * @param k index of the current step + * @param b2k coefficient to initialize (b2k = a2k / a1k) + * @param b3k coefficient to initialize (b3k = a3k / a1k) + * @param b4k coefficient to initialize (b4k = a4k / a1k) + */ + protected void initRecurrenceCoefficients(int k, + RationalNumber b2k, + RationalNumber b3k, + RationalNumber b4k) { + long kP1 = k + 1; + b2k.reset(2 * k + 1, kP1); + b3k.reset(-1l, kP1); + b4k.reset(k, kP1); + } + + /** Set the maximal degree of already computed polynomials. + * @param d maximal degree of already computed polynomials + */ + protected void setMaxDegree(int d) { + maxDegree = d; + } + + private static final long serialVersionUID = -750526984136835515L; + + /** List holding the coefficients of the polynomials computed so far. */ + private static List l; + + /** Maximal degree of the polynomials computed so far. */ + private static int maxDegree; + + /** Build the first two polynomials. */ + static { + + l = new ArrayList (); + + // L0(X) = 1 + l.add(new RationalNumber(1l)); + + // L1(X) = 1 - X + l.add(new RationalNumber(1l)); + l.add(new RationalNumber(-1l)); + + maxDegree = 1; + + } + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/algebra/Legendre.java b/src/mantissa/src/org/spaceroots/mantissa/algebra/Legendre.java new file mode 100755 index 000000000..5d2b65592 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/algebra/Legendre.java @@ -0,0 +1,106 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.algebra; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class implements Legendre polynomials. + + *

Legendre polynomials can be defined by the following recurrence + * relations: + *

+ *        P0(X)   = 1
+ *        P1(X)   = X
+ *  (k+1) Pk+1(X) = (2k+1) X Pk(X) - k Pk-1(X)
+ * 

+ + * @version $Id: Legendre.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class Legendre + extends OrthogonalPolynomial { + + /** Simple constructor. + * Build a degree 0 Legendre polynomial + */ + public Legendre() { + super(0, l, maxDegree); + } + + /** Simple constructor. + * Build a degree d Legendre polynomial + * @param d degree of the polynomial + */ + public Legendre(int d) { + super(d, l, maxDegree); + } + + /** Initialize the recurrence coefficients. + * The recurrence relation is + *
(k+1) Pk+1(X) = (2k+1) X Pk(X) - k Ok-1(X)
+ * @param k index of the current step + * @param b2k coefficient to initialize (b2k = a2k / a1k) + * @param b3k coefficient to initialize (b3k = a3k / a1k) + * @param b4k coefficient to initialize (b4k = a4k / a1k) + */ + protected void initRecurrenceCoefficients(int k, + RationalNumber b2k, + RationalNumber b3k, + RationalNumber b4k) { + long kP1 = k + 1; + b2k.reset(0l); + b3k.reset(2 * k + 1, kP1); + b4k.reset(k, kP1); + } + + /** Set the maximal degree of already computed polynomials. + * @param d maximal degree of already computed polynomials + */ + protected void setMaxDegree(int d) { + maxDegree = d; + } + + private static final long serialVersionUID = 428266828791532209L; + + /** List holding the coefficients of the polynomials computed so far. */ + private static List l; + + /** Maximal degree of the polynomials computed so far. */ + private static int maxDegree; + + /** Build the first two polynomials. */ + static { + + l = new ArrayList (); + + // P0(X) = 1 + l.add(new RationalNumber(1l)); + + // P1(X) = X + l.add(new RationalNumber(0l)); + l.add(new RationalNumber(1l)); + + maxDegree = 1; + + } + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/algebra/OrthogonalPolynomial.java b/src/mantissa/src/org/spaceroots/mantissa/algebra/OrthogonalPolynomial.java new file mode 100644 index 000000000..a8cc873b3 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/algebra/OrthogonalPolynomial.java @@ -0,0 +1,151 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.algebra; + +import java.util.List; + +/** + * This class is the base class for orthogonal polynomials. + + *

Orthogonal polynomials can be defined by recurrence relations like: + *

+ *      O0(X)   = some 0 degree polynomial
+ *      O1(X)   = some first degree polynomial
+ *  a1k Ok+1(X) = (a2k + a3k X) Ok(X) - a4k Ok-1(X)
+ * 
+ * where a0k, a1k, a2k and a3k are simple expressions which either are + * constants or depend on k.

+ + * @version $Id: OrthogonalPolynomial.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ +public abstract class OrthogonalPolynomial + extends Polynomial.Rational { + + /** Simple constructor. + * Build a degree d orthogonal polynomial + * @param d degree of the polynomial + * @param l list containing all coefficients already computed + * @param maxDegree maximal degree of computed coefficients, this + * coefficient must be greater or equal to 1, i.e. the + * derived class must have initialized the first two + * polynomials of degree 0 and 1 before this constructor can be + * called. + */ + protected OrthogonalPolynomial(int d, List l, int maxDegree) { + if (d > maxDegree) { + computeUpToDegree(d, l, maxDegree); + } + + // coefficient for polynomial 0 is l [0] + // coefficient for polynomial 1 are l [1] ... l [2] (degrees 0 ... 1) + // coefficients for polynomial 2 are l [3] ... l [5] (degrees 0 ... 2) + // coefficients for polynomial 3 are l [6] ... l [9] (degrees 0 ... 3) + // coefficients for polynomial 4 are l[10] ... l[14] (degrees 0 ... 4) + // coefficients for polynomial 5 are l[15] ... l[20] (degrees 0 ... 5) + // coefficients for polynomial 6 are l[21] ... l[27] (degrees 0 ... 6) + // ... + int start = d * (d + 1) / 2; + + a = new RationalNumber[d+1]; + for (int i = 0; i <= d; ++i) { + a[i] = new RationalNumber((RationalNumber) l.get(start + i)); + } + + unknown = null; + + } + + /** Initialize the recurrence coefficients. + * The recurrence relation is + *
a1k Ok+1(X) = (a2k + a3k X) Ok(X) - a4k Ok-1(X)
+ * @param k index of the current step + * @param b2k coefficient to initialize (b2k = a2k / a1k) + * @param b3k coefficient to initialize (b3k = a3k / a1k) + * @param b4k coefficient to initialize (b4k = a4k / a1k) + */ + protected abstract void initRecurrenceCoefficients(int k, + RationalNumber b2k, + RationalNumber b3k, + RationalNumber b4k); + + /** Set the maximal degree of already computed polynomials. + * @param d maximal degree of already computed polynomials + */ + protected abstract void setMaxDegree(int d); + + /** Compute all the polynomial coefficients up to a given degree. + * @param d maximal degree + * @param l list containing all coefficients already computed + * @param maxDegree maximal degree of computed coefficients + */ + protected void computeUpToDegree(int d, List l, int maxDegree) { + + RationalNumber b2k = new RationalNumber(); + RationalNumber b3k = new RationalNumber(); + RationalNumber b4k = new RationalNumber(); + + int startK = (maxDegree - 1) * maxDegree / 2; + for (int k = maxDegree; k < d; ++k) { + + // start indices of two previous polynomials Ok(X) and Ok-1(X) + int startKm1 = startK; + startK += k; + + // a1k Ok+1(X) = (a2k + a3k X) Ok(X) - a4k Ok-1(X) + // we use bik = aik/a1k + initRecurrenceCoefficients(k, b2k, b3k, b4k); + + RationalNumber ckPrev = null; + RationalNumber ck = (RationalNumber)l.get(startK); + RationalNumber ckm1 = (RationalNumber)l.get(startKm1); + + // degree 0 coefficient + RationalNumber coeff = RationalNumber.multiply(ck, b2k); + coeff.multiplyAndSubtractFromSelf(ckm1, b4k); + l.add(coeff); + + // degree 1 to degree k-1 coefficients + for (int i = 1; i < k; ++i) { + ckPrev = ck; + ck = (RationalNumber)l.get(startK + i); + ckm1 = (RationalNumber)l.get(startKm1 + i); + coeff = RationalNumber.multiply(ck, b2k); + coeff.multiplyAndAddToSelf(ckPrev, b3k); + coeff.multiplyAndSubtractFromSelf(ckm1, b4k); + l.add(coeff); + } + + // degree k coefficient + ckPrev = ck; + ck = (RationalNumber)l.get(startK + k); + coeff = RationalNumber.multiply(ck, b2k); + coeff.multiplyAndAddToSelf(ckPrev, b3k); + l.add(coeff); + + // degree k+1 coefficient + l.add(RationalNumber.multiply(ck, b3k)); + + } + + setMaxDegree(d); + + } + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/algebra/Polynomial.java b/src/mantissa/src/org/spaceroots/mantissa/algebra/Polynomial.java new file mode 100755 index 000000000..65e6ea617 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/algebra/Polynomial.java @@ -0,0 +1,1202 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.algebra; + +import java.io.Serializable; +import java.math.BigInteger; + +/** + * This class implements polynomials with one unknown. + + *

This is an abstract class that only declares general methods but + * does not hold the coefficients by themselves. Specific subclasses + * are used to handle exact rational coefficients or approximate real + * coefficients. This design is taken from the various java.awt.geom + * classes (Point2D, Rectangle2D ...)

+ + *

The methods implemented deal mainly with the polynomials algebra + * (addition, multiplication ...) but the analysis aspects are also + * considered (value of the polynom for a given unknown, + * derivative).

+ + * @version $Id: Polynomial.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + +*/ +public abstract class Polynomial + implements Cloneable, Serializable { + + /** Create a copy of the instance. + * @return a copy of the instance + */ + public abstract Object clone(); + + /** Check if the instance is the null polynomial. + * @return true if the polynomial is null + */ + public abstract boolean isZero(); + + /** Check if the instance is the constant unit polynomial. + * @return true if the polynomial is the constant unit polynomial + */ + public abstract boolean isOne(); + + /** Check if the instance is the identity polynomial. + * @return true if the polynomial is the identity polynomial + */ + public abstract boolean isIdentity(); + + /** Get the polynomial degree. + * @return degree + */ + public abstract int getDegree(); + + /** Negate the instance. + */ + public abstract void negateSelf(); + + /** Multiply the instance by a constant. + * @param r constant to multiply by + */ + public abstract void multiplySelf(RationalNumber r); + + /** Multiply the instance by a constant. + * @param l constant to multiply by + */ + public abstract void multiplySelf(long l); + + /** Multiply the instance by a constant. + * @param i constant to multiply by + */ + public void multiplySelf(BigInteger i) { + multiplySelf(new RationalNumber(i)); + } + + /** Get the value of the polynomial for a specified unknown. + * @param x value of the unknown + * @return value of the polynomial + */ + public abstract double valueAt(double x); + + /** Get the derivative of the instance with respect to the unknown. + * The derivative of a n degree polynomial is a n-1 degree polynomial of + * the same type. + * @return a new polynomial which is the derivative of the instance + */ + public abstract Polynomial getDerivative(); + + /** Set the name of the unknown (to appear during conversions to strings). + * @param name name to set (if null, the default 'x' value will be used) + */ + public abstract void setUnknownName(String name); + + /** This class implements polynomials with one unknown and rational + * coefficients. + + *

In addition to classical algebra operations, euclidian + * division and remainder are handled.

+ + */ + public static class Rational extends Polynomial { + + /** Simple constructor. + * Build a null polynomial + */ + public Rational() { + a = new RationalNumber[1]; + a[0] = new RationalNumber(0l); + unknown = null; + } + + /** Simple constructor. + * Build a constant polynomial + * @param value constant value of the polynomial + */ + public Rational(long value) { + this(new RationalNumber(value)); + } + + /** Simple constructor. + * Build a constant polynomial + * @param value constant value of the polynomial + */ + public Rational(RationalNumber value) { + a = new RationalNumber[1]; + a[0] = value; + unknown = null; + } + + /** Simple constructor. + * Build a first degree polynomial + * @param a1 leeding degree coefficient + * @param a0 constant term + */ + public Rational(long a1, long a0) { + this(new RationalNumber(a1), new RationalNumber(a0)); + } + + /** Simple constructor. + * Build a first degree polynomial + * @param a1 leeding degree coefficient + * @param a0 constant term + */ + public Rational(RationalNumber a1, RationalNumber a0) { + if (! a1.isZero()) { + a = new RationalNumber[2]; + a[1] = a1; + } else { + a = new RationalNumber[1]; + } + a[0] = a0; + unknown = null; + } + + /** Simple constructor. + * Build a second degree polynomial + * @param a2 leeding degree coefficient + * @param a1 first degree coefficient + * @param a0 constant term + */ + public Rational(long a2, long a1, long a0) { + this(new RationalNumber(a2), + new RationalNumber(a1), + new RationalNumber(a0)); + } + + /** Simple constructor. + * Build a second degree polynomial + * @param a2 leeding degree coefficient + * @param a1 first degree coefficient + * @param a0 constant term + */ + public Rational(RationalNumber a2, RationalNumber a1, RationalNumber a0) { + if (! a2.isZero()) { + a = new RationalNumber[3]; + a[2] = a2; + a[1] = a1; + } else { + if (! a1.isZero()) { + a = new RationalNumber[2]; + a[1] = a1; + } else { + a = new RationalNumber[1]; + } + } + a[0] = a0; + unknown = null; + } + + /** Simple constructor. + * Build a polynomial from its coefficients + * @param a coefficients array, the a[0] array element is the + * constant term while the a[a.length-1] element is the leeding + * degree coefficient. The array is copied in a new array, so it + * can be changed once the constructor as returned. + */ + public Rational(RationalNumber[] a) { + + // remove null high degree coefficients + int i = a.length - 1; + while ((i > 0) && (a[i].isZero())) { + --i; + } + + // copy the remaining coefficients + this.a = new RationalNumber[i + 1]; + System.arraycopy(a, 0, this.a, 0, i + 1); + + unknown = null; + + } + + /** Simple constructor. + * Build a one term polynomial from one coefficient and the corresponding degree + * @param c coefficient + * @param degree degree associated with the coefficient + */ + public Rational(RationalNumber c, int degree) { + + if (c.isZero() || degree < 0) { + a = new RationalNumber[1]; + a[0] = new RationalNumber(0l); + } else { + a = new RationalNumber[degree + 1]; + for (int i = 0; i < degree; ++i) { + a[i] = new RationalNumber(0l); + } + a[degree] = new RationalNumber(c); + } + + unknown = null; + + } + + /** Copy constructor. + * The copy is a deep copy: the polynomials do not share + * their coefficients arrays + * @param p polynomial to copy + */ + public Rational(Rational p) { + + a = new RationalNumber[p.a.length]; + for (int i = 0; i < a.length; ++i) { + a[i] = new RationalNumber(p.a[i]); + } + + if (p.unknown == null) { + unknown = null; + } else { + unknown = new String(p.unknown); + } + + } + + /** Create a copy of the instance. + * @return a copy of the instance + */ + public Object clone() { + return new Rational(this); + } + + /** Check if the instance is the null polynomial. + * @return true if the polynomial is null + */ + public boolean isZero() { + return (a.length == 1) && a[0].isZero(); + } + + /** Check if the instance is the constant unit polynomial. + * @return true if the polynomial is the constant unit polynomial + */ + public boolean isOne() { + return (a.length == 1) && a[0].isOne(); + } + + /** Check if the instance is the identity polynomial. + * @return true if the polynomial is the identity polynomial + */ + public boolean isIdentity() { + return (a.length == 2) && a[0].isZero() && a[1].isOne(); + } + + /** Get the polynomial degree. + * @return degree + */ + public int getDegree() { + return a.length - 1; + } + + /** Get the coefficients of the polynomial. + * @return a reference to the internal coefficients array, the array + * element at index 0 is the constant term while the element at + * index a.length-1 is the leeding degree coefficient + */ + public RationalNumber[] getCoefficients() { + return a; + } + + /** Set the name of the unknown (to appear during conversions to strings). + * @param name name to set (if null, the default 'x' value will be used) + */ + public void setUnknownName(String name) { + unknown = name; + } + + /** Simplify the polynomial, by removing null high degree terms. + */ + private void simplify() { + + int i = a.length - 1; + while ((i > 0) && a[i].isZero()) { + --i; + } + + if (i < a.length - 1) { + RationalNumber[] newA = new RationalNumber[i + 1]; + System.arraycopy(a, 0, newA, 0, i + 1); + a = newA; + } + + } + + /** Add a polynomial to the instance. + * @param p polynomial to add + */ + public void addToSelf(Rational p) { + + if (p.a.length > a.length) { + RationalNumber[] newA = new RationalNumber[p.a.length]; + System.arraycopy(a, 0, newA, 0, a.length); + for (int i = a.length; i < newA.length; ++i) { + newA[i] = new RationalNumber(0l); + } + a = newA; + } + + for (int i = 0; i < p.a.length; ++i) { + a[i].addToSelf(p.a[i]); + } + + simplify(); + + } + + /** Add two polynomials. + * @param p1 first polynomial + * @param p2 second polynomial + * @return a new polynomial which is the sum of p1 and p2 + */ + public static Rational add(Rational p1, Rational p2) { + Rational copy = new Rational(p1); + copy.addToSelf(p2); + return copy; + } + + /** Subtract a polynomial from the instance. + * @param p polynomial to subtract + */ + public void subtractFromSelf(Rational p) { + + if (p.a.length > a.length) { + RationalNumber[] newA = new RationalNumber[p.a.length]; + System.arraycopy(a, 0, newA, 0, a.length); + for (int i = a.length; i < newA.length; ++i) { + newA[i] = new RationalNumber(0l); + } + a = newA; + } + + for (int i = 0; i < p.a.length; ++i) { + a[i].subtractFromSelf(p.a[i]); + } + + simplify(); + + } + + /** Subtract two polynomials. + * @param p1 first polynomial + * @param p2 second polynomial + * @return a new polynomial which is the difference p1 minus p2 + */ + public static Rational subtract(Rational p1, Rational p2) { + Rational copy = new Rational(p1); + copy.subtractFromSelf(p2); + return copy; + } + + /** Negate the instance. + */ + public void negateSelf() { + for (int i = 0; i < a.length; ++i) { + a[i].negateSelf(); + } + } + + /** Negate a polynomial. + * @param p polynomial to negate + * @return a new polynomial which is the opposite of p + */ + public static Rational negate(Rational p) { + Rational copy = new Rational(p); + copy.negateSelf(); + return copy; + } + + /** Multiply the instance by a polynomial. + * @param p polynomial to multiply by + */ + public void multiplySelf(Rational p) { + + RationalNumber[] newA = new RationalNumber[a.length + p.a.length - 1]; + + for (int i = 0; i < newA.length; ++i) { + newA[i] = new RationalNumber(0l); + for (int j = Math.max(0, i + 1 - p.a.length); + j < Math.min(a.length, i + 1); + ++j) { + newA[i].addToSelf(RationalNumber.multiply(a[j], p.a[i-j])); + } + } + + a = newA; + + } + + /** Multiply two polynomials. + * @param p1 first polynomial + * @param p2 second polynomial + * @return a new polynomial which is the product of p1 and p2 + */ + public static Rational multiply(Rational p1, Rational p2) { + Rational copy = new Rational(p1); + copy.multiplySelf(p2); + return copy; + } + + /** Multiply the instance by a constant. + * @param r constant to multiply by + */ + public void multiplySelf(RationalNumber r) { + + if (r.isZero()) { + a = new RationalNumber[1]; + a[0] = new RationalNumber(0l); + } + + for (int i = 0; i < a.length; ++i) { + a[i].multiplySelf(r); + } + + } + + /** Multiply a polynomial by a constant. + * @param p polynomial + * @param r constant + * @return a new polynomial which is the product of p and r + */ + public static Rational multiply(Rational p, RationalNumber r) { + Rational copy = new Rational(p); + copy.multiplySelf(r); + return copy; + } + + /** Multiply the instance by a constant. + * @param l constant to multiply by + */ + public void multiplySelf(long l) { + + if (l == 0l) { + a = new RationalNumber[1]; + a[0] = new RationalNumber(0l); + } + + for (int i = 0; i < a.length; ++i) { + a[i].multiplySelf(l); + } + + } + + /** Multiply a polynomial by a constant. + * @param p polynomial + * @param l constant + * @return a new polynomial which is the product of p and l + */ + public static Rational multiply(Rational p, long l) { + Rational copy = new Rational(p); + copy.multiplySelf(l); + return copy; + } + + /** Get the value of the polynomial for a specified unknown. + * @param x value of the unknown + * @return value of the polynomial + */ + public double valueAt(double x) { + double y = 0; + for (int i = a.length - 1; i >= 0; --i) { + y = y * x + a[i].doubleValue(); + } + return y; + } + + /** Get the derivative of the instance with respect to the unknown. + * The derivative of a n degree polynomial is a n-1 degree polynomial of + * the same type. + * @return a new polynomial which is the derivative of the instance + */ + public Polynomial getDerivative() { + Rational derivative = new Rational(); + if (a.length == 1) { + return derivative; + } + derivative.a = new RationalNumber[a.length - 1]; + for (int i = 1; i < a.length; ++i) { + derivative.a[i-1] = RationalNumber.multiply(a[i], i); + } + return derivative; + } + + /** Perform the euclidian division of two polynomials. + * @param dividend numerator polynomial + * @param divisor denominator polynomial + * @return an object containing the quotient and the remainder of the division + */ + public static DivisionResult euclidianDivision(Rational dividend, + Rational divisor) { + + Rational quotient = new Rational(0l); + Rational remainder = new Rational(dividend); + + int divisorDegree = divisor.getDegree(); + int remainderDegree = remainder.getDegree(); + while ((! remainder.isZero()) && (remainderDegree >= divisorDegree)) { + + RationalNumber c = RationalNumber.divide(remainder.a[remainderDegree], + divisor.a[divisorDegree]); + Rational monomial = new Rational(c, remainderDegree - divisorDegree); + + remainder.subtractFromSelf(Rational.multiply(monomial, divisor)); + quotient.addToSelf(monomial); + + remainderDegree = remainder.getDegree(); + + } + + return new DivisionResult(quotient, remainder); + + } + + /** Get the Least Common Multiple of the coefficients denominators. + * This number is the smallest integer by which we should multiply + * the instance to get a polynomial whose coefficients are all integers. + * @return the Least Common Multiple of the coefficients denominators + */ + public BigInteger getDenominatorsLCM() { + + BigInteger lcm = BigInteger.ONE; + + for (int i = 0; i < a.length; ++i) { + RationalNumber newCoeff = RationalNumber.multiply(a[i], lcm); + if (! newCoeff.isInteger()) { + lcm = lcm.multiply(newCoeff.getDenominator()); + } + } + + return lcm; + + } + + /** Returns a string representation of the polynomial. + + *

The representation is user oriented. Terms are displayed lowest + * degrees first. The multiplications signs, coefficients equals to + * one and null terms are not displayed (except if the polynomial is 0, + * in which case the 0 constant term is displayed). Addition of terms + * with negative coefficients are replaced by subtraction of terms + * with positive coefficients except for the first displayed term + * (i.e. we display -3 for a constant negative polynomial, + * but 1 - 3 x + x^2 if the negative coefficient is not + * the first one displayed).

+ + *

The name of the unknown is x by default, but can + * be changed using the {@link #setUnknownName setUnknownName} + * method.

+ + * @return a string representation of the polynomial + + */ + public String toString() { + + StringBuffer s = new StringBuffer(); + if (a[0].isZero()) { + if (a.length == 1) { + return "0"; + } + } else { + s.append(a[0].toString()); + } + + for (int i = 1; i < a.length; ++i) { + + if (! a[i].isZero()) { + + if (s.length() > 0) { + if (a[i].isNegative()) { + s.append(" - "); + } else { + s.append(" + "); + } + } else { + if (a[i].isNegative()) { + s.append("-"); + } + } + + RationalNumber absAi = RationalNumber.abs(a[i]); + if (! absAi.isOne()) { + s.append(absAi.toString()); + s.append(' '); + } + + s.append((unknown == null) ? defaultUnknown : unknown); + if (i > 1) { + s.append('^'); + s.append(Integer.toString(i)); + } + } + + } + + return s.toString(); + + } + + /** Coefficients array. */ + protected RationalNumber[] a; + + /** Name of the unknown. */ + protected String unknown; + + private static final long serialVersionUID = 3035650338772911046L; + + } + + /** This class stores the result of the euclidian division of two polynomials. + * This class is a simple placeholder, it does not provide any + * processing method + * @see Polynomial.Rational#euclidianDivision + */ + public static class DivisionResult { + + /** The quotient of the division. */ + public final Rational quotient; + + /** The remainder of the division. */ + public final Rational remainder; + + /** Simple constructor. */ + public DivisionResult(Rational quotient, Rational remainder) { + this.quotient = quotient; + this.remainder = remainder; + } + + } + + /** This class implements polynomials with one unknown and real + * coefficients. + */ + public static class Double extends Polynomial { + + /** Simple constructor. + * Build a null polynomial + */ + public Double() { + a = new double[1]; + a[0] = 0; + unknown = null; + } + + /** Simple constructor. + * Build a constant polynomial + * @param value constant value of the polynomial + */ + public Double(double value) { + a = new double[1]; + a[0] = value; + unknown = null; + } + + /** Simple constructor. + * Build a first degree polynomial + * @param a1 leeding degree coefficient + * @param a0 constant term + */ + public Double(double a1, double a0) { + if (Math.abs(a1) > 1.0e-12) { + a = new double[2]; + a[1] = a1; + } else { + a = new double[1]; + } + a[0] = a0; + unknown = null; + } + + /** Simple constructor. + * Build a second degree polynomial + * @param a2 leeding degree coefficient + * @param a1 first degree coefficient + * @param a0 constant term + */ + public Double(double a2, double a1, double a0) { + if (Math.abs(a2) > 1.0e-12) { + a = new double[3]; + a[2] = a2; + a[1] = a1; + } else { + if (Math.abs(a1) > 1.0e-12) { + a = new double[2]; + a[1] = a1; + } else { + a = new double[1]; + } + } + a[0] = a0; + unknown = null; + } + + /** Simple constructor. + * Build a polynomial from its coefficients + * @param a coefficients array, the a[0] array element is the + * constant term while the a[a.length-1] element is the leeding + * degree coefficient. The array is copied in a new array, so it + * can be changed once the constructor as returned. + */ + public Double(double[] a) { + + // remove null high degree coefficients + int i = a.length - 1; + while ((i > 0) && (Math.abs(a[i]) <= 1.0e-12)) { + --i; + } + + // copy the remaining coefficients + this.a = new double[i + 1]; + System.arraycopy(a, 0, this.a, 0, i + 1); + + unknown = null; + + } + + /** Simple constructor. + * Build a one term polynomial from one coefficient and the corresponding degree + * @param c coefficient + * @param degree degree associated with the coefficient + */ + public Double(double c, int degree) { + + if ((Math.abs(c) <= 1.0e-12) || degree < 0) { + a = new double[1]; + a[0] = 0; + } else { + a = new double[degree + 1]; + for (int i = 0; i < degree; ++i) { + a[i] = 0; + } + a[degree] = c; + } + + unknown = null; + + } + + /** Copy constructor. + * The copy is a deep copy: the polynomials do not share + * their coefficients arrays + * @param p polynomial to copy + */ + public Double(Double p) { + + a = new double[p.a.length]; + for (int i = 0; i < a.length; ++i) { + a[i] = p.a[i]; + } + + if (p.unknown == null) { + unknown = null; + } else { + unknown = new String(p.unknown); + } + + } + + /** Copy constructor. + * The copy is a deep copy: the polynomials do not share + * their coefficients arrays + * @param p polynomial to copy + */ + public Double(Rational p) { + + RationalNumber[] pA = p.getCoefficients(); + a = new double[pA.length]; + for (int i = 0; i < a.length; ++i) { + a[i] = pA[i].doubleValue(); + } + + if (p.unknown == null) { + unknown = null; + } else { + unknown = new String(p.unknown); + } + + } + + /** Create a copy of the instance. + * @return a copy of the instance + */ + public Object clone() { + return new Double(this); + } + + /** Check if the instance is the null polynomial. + * @return true if the polynomial is null + */ + public boolean isZero() { + return (a.length == 1) && (Math.abs(a[0]) < 1.0e-12); + } + + /** Check if the instance is the constant unit polynomial. + * @return true if the polynomial is the constant unit polynomial + */ + public boolean isOne() { + return (a.length == 1) && (Math.abs(a[0] - 1) < 1.0e-12); + } + + /** Check if the instance is the identity polynomial. + * @return true if the polynomial is the identity polynomial + */ + public boolean isIdentity() { + return (a.length == 2) + && (Math.abs(a[0]) < 1.0e-12) + && (Math.abs(a[1] - 1) < 1.0e-12); + } + + /** Get the polynomial degree. + * @return degree + */ + public int getDegree() { + return a.length - 1; + } + + /** Get the coefficients of the polynomial. + * @return a reference to the internal coefficients array, the array + * element at index 0 is the constant term while the element at + * index a.length-1 is the leeding degree coefficient + */ + public double[] getCoefficients() { + return a; + } + + /** Simplify the polynomial, by removing null high degree terms. + */ + private void simplify() { + + int i = a.length - 1; + while ((i > 0) && (Math.abs(a[i]) <= 1.0e-12)) { + --i; + } + + if (i < a.length - 1) { + double[] newA = new double[i + 1]; + System.arraycopy(a, 0, newA, 0, i + 1); + a = newA; + } + + } + + /** Add a polynomial to the instance. + * @param p polynomial to add + */ + public void addToSelf(Double p) { + + if (p.a.length > a.length) { + double[] newA = new double[p.a.length]; + System.arraycopy(a, 0, newA, 0, a.length); + for (int i = a.length; i < newA.length; ++i) { + newA[i] = 0; + } + a = newA; + } + + for (int i = 0; i < p.a.length; ++i) { + a[i] += p.a[i]; + } + + simplify(); + + } + + /** Add two polynomials. + * @param p1 first polynomial + * @param p2 second polynomial + * @return a new polynomial which is the sum of p1 and p2 + */ + public static Double add(Double p1, Double p2) { + Double copy = new Double(p1); + copy.addToSelf(p2); + return copy; + } + + /** Subtract a polynomial from the instance. + * @param p polynomial to subtract + */ + public void subtractFromSelf(Double p) { + + if (p.a.length > a.length) { + double[] newA = new double[p.a.length]; + System.arraycopy(a, 0, newA, 0, a.length); + for (int i = a.length; i < newA.length; ++i) { + newA[i] = 0; + } + a = newA; + } + + for (int i = 0; i < p.a.length; ++i) { + a[i] -= p.a[i]; + } + + simplify(); + + } + + /** Subtract two polynomials. + * @param p1 first polynomial + * @param p2 second polynomial + * @return a new polynomial which is the difference p1 minus p2 + */ + public static Double subtract(Double p1, Double p2) { + Double copy = new Double(p1); + copy.subtractFromSelf(p2); + return copy; + } + + /** Negate the instance. + */ + public void negateSelf() { + for (int i = 0; i < a.length; ++i) { + a[i] = -a[i]; + } + } + + /** Negate a polynomial. + * @param p polynomial to negate + * @return a new polynomial which is the opposite of p + */ + public static Double negate(Double p) { + Double copy = new Double(p); + copy.negateSelf(); + return copy; + } + + /** Multiply the instance by a polynomial. + * @param p polynomial to multiply by + */ + public void multiplySelf(Double p) { + + double[] newA = new double[a.length + p.a.length - 1]; + + for (int i = 0; i < newA.length; ++i) { + newA[i] = 0; + for (int j = Math.max(0, i + 1 - p.a.length); + j < Math.min(a.length, i + 1); + ++j) { + newA[i] += a[j] * p.a[i-j]; + } + } + + a = newA; + + } + + /** Multiply two polynomials. + * @param p1 first polynomial + * @param p2 second polynomial + * @return a new polynomial which is the product of p1 and p2 + */ + public static Double multiply(Double p1, Double p2) { + Double copy = new Double(p1); + copy.multiplySelf(p2); + return copy; + } + + /** Multiply the instance by a constant. + * @param r constant to multiply by + */ + public void multiplySelf(double r) { + + if (Math.abs(r) < 1.0e-12) { + a = new double[1]; + a[0] = 0; + } + + for (int i = 0; i < a.length; ++i) { + a[i] *= r; + } + + } + + /** Multiply a polynomial by a constant. + * @param p polynomial + * @param r constant + * @return a new polynomial which is the product of p and r + */ + public static Double multiply(Double p, double r) { + Double copy = new Double(p); + copy.multiplySelf(r); + return copy; + } + + /** Multiply the instance by a constant. + * @param r constant to multiply by + */ + public void multiplySelf(RationalNumber r) { + + if (r.isZero()) { + a = new double[1]; + a[0] = 0; + } + + double rValue = r.doubleValue(); + for (int i = 0; i < a.length; ++i) { + a[i] *= rValue; + } + + } + + /** Multiply the instance by a constant. + * @param l constant to multiply by + */ + public void multiplySelf(long l) { + + if (l == 0l) { + a = new double[1]; + a[0] = 0; + } + + for (int i = 0; i < a.length; ++i) { + a[i] *= l; + } + + } + + /** Multiply a polynomial by a constant. + * @param p polynomial + * @param l constant + * @return a new polynomial which is the product of p and l + */ + public static Double multiply(Double p, long l) { + Double copy = new Double(p); + copy.multiplySelf(l); + return copy; + } + + /** Get the value of the polynomial for a specified unknown. + * @param x value of the unknown + * @return value of the polynomial + */ + public double valueAt(double x) { + double y = 0; + for (int i = a.length - 1; i >= 0; --i) { + y = y * x + a[i]; + } + return y; + } + + /** Get the derivative of the instance with respect to the unknown. + * The derivative of a n degree polynomial is a n-1 degree polynomial of + * the same type. + * @return a new polynomial which is the derivative of the instance + */ + public Polynomial getDerivative() { + Double derivative = new Double(); + if (a.length == 1) { + return derivative; + } + derivative.a = new double[a.length - 1]; + for (int i = 1; i < a.length; ++i) { + derivative.a[i-1] = a[i] * i; + } + return derivative; + } + + /** Set the name of the unknown (to appear during conversions to strings). + * @param name name to set (if null, the default 'x' value will be used) + */ + public void setUnknownName(String name) { + unknown = name; + } + + /** Returns a string representation of the polynomial. + + *

The representation is user oriented. Terms are displayed lowest + * degrees first. The multiplications signs, coefficients equals to + * one and null terms are not displayed (except if the polynomial is 0, + * in which case the 0 constant term is displayed). Addition of terms + * with negative coefficients are replaced by subtraction of terms + * with positive coefficients except for the first displayed term + * (i.e. we display -3 for a constant negative polynomial, + * but 1 - 3 x + x^2 if the negative coefficient is not + * the first one displayed).

+ + *

The name of the unknown is x by default, but can + * be changed using the {@link #setUnknownName setUnknownName} + * method.

+ + * @return a string representation of the polynomial + + */ + public String toString() { + + double maxCoeff = 0; + for (int i = 0; i < a.length; ++i) { + double abs = Math.abs(a[i]); + if (abs > maxCoeff) { + maxCoeff = abs; + } + } + double epsilon = 1.0e-12 * maxCoeff; + + StringBuffer s = new StringBuffer(); + if (Math.abs(a[0]) <= epsilon) { + if (a.length == 1) { + return "0"; + } + } else { + s.append(a[0]); + } + + for (int i = 1; i < a.length; ++i) { + + if (Math.abs(a[i]) > epsilon) { + + if (s.length() > 0) { + if (a[i] < 0) { + s.append(" - "); + } else { + s.append(" + "); + } + } else { + if (a[i] < 0) { + s.append("-"); + } + } + + double absAi = Math.abs(a[i]); + if (Math.abs(absAi - 1) > 1.0e-12) { + s.append(absAi); + s.append(' '); + } + + s.append((unknown == null) ? defaultUnknown : unknown); + if (i > 1) { + s.append('^'); + s.append(Integer.toString(i)); + } + } + + } + + return s.toString(); + + } + + /** Coefficients array. */ + protected double[] a; + + /** Name of the unknown. */ + protected String unknown; + + private static final long serialVersionUID = -5907669461605191069L; + + } + + /** Default name of unknowns. */ + protected static String defaultUnknown = new String("x"); + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/algebra/PolynomialFraction.java b/src/mantissa/src/org/spaceroots/mantissa/algebra/PolynomialFraction.java new file mode 100755 index 000000000..2288c7f58 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/algebra/PolynomialFraction.java @@ -0,0 +1,439 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.algebra; + +import java.math.BigInteger; + +/** + * This class implements fractions of polynomials with one unknown and + * rational coefficients. + + * @version $Id: PolynomialFraction.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class PolynomialFraction { + + /** + * Simple constructor. + * Build a constant null fraction + */ + public PolynomialFraction() { + this(new Polynomial.Rational(new RationalNumber(0l)), + new Polynomial.Rational(new RationalNumber(1l))); + } + + /** + * Simple constructor. + * Build a fraction from a numerator and a denominator. + * @param numerator numerator of the fraction + * @param denominator denominator of the fraction + * @exception ArithmeticException if the denominator is null + */ + public PolynomialFraction(long numerator, long denominator) { + this(new Polynomial.Rational(new RationalNumber(numerator)), + new Polynomial.Rational(new RationalNumber(denominator))); + } + + /** + * Simple constructor. + * Build a fraction from a numerator and a denominator. + * @param numerator numerator of the fraction + * @param denominator denominator of the fraction + * @exception ArithmeticException if the denominator is null + */ + public PolynomialFraction(BigInteger numerator, BigInteger denominator) { + this(new Polynomial.Rational(new RationalNumber(numerator)), + new Polynomial.Rational(new RationalNumber(denominator))); + } + + /** + * Simple constructor. + * Build a fraction from a numerator and a denominator. + * @param numerator numerator of the fraction + * @param denominator denominator of the fraction + * @exception ArithmeticException if the denominator is null + */ + public PolynomialFraction(RationalNumber numerator, + RationalNumber denominator) { + this(new Polynomial.Rational(numerator), + new Polynomial.Rational(denominator)); + } + + /** + * Simple constructor. + * Build a fraction from a numerator and a denominator. + * @param numerator numerator of the fraction + * @param denominator denominator of the fraction + * @exception ArithmeticException if the denominator is null + */ + public PolynomialFraction(Polynomial.Rational numerator, + Polynomial.Rational denominator) { + + if (denominator.isZero()) { + throw new ArithmeticException("null denominator"); + } + + p = new Polynomial.Rational(numerator); + q = new Polynomial.Rational(denominator); + + RationalNumber[] a = q.getCoefficients(); + if (a[a.length - 1].isNegative()) { + p.negateSelf(); + q.negateSelf(); + } + + simplify(); + + } + + /** + * Simple constructor. + * Build a fraction from a single integer + * @param l value of the fraction + */ + public PolynomialFraction(long l) { + this(l, 1l); + } + + /** + * Simple constructor. + * Build a fraction from a single integer + * @param i value of the fraction + */ + public PolynomialFraction(BigInteger i) { + this(i, BigInteger.ONE); + } + + /** + * Simple constructor. + * Build a fraction from a single rational number + * @param r value of the fraction + */ + public PolynomialFraction(RationalNumber r) { + this(r.getNumerator(), r.getDenominator()); + } + + /** + * Simple constructor. + * Build a fraction from a single Polynom + * @param p value of the fraction + */ + public PolynomialFraction(Polynomial.Rational p) { + this(p, new Polynomial.Rational(new RationalNumber(1l))); + } + + /** + * Copy-constructor. + * @param f fraction to copy + */ + public PolynomialFraction(PolynomialFraction f) { + p = new Polynomial.Rational(f.p); + q = new Polynomial.Rational(f.q); + } + + /** + * Negate the instance + */ + public void negateSelf() { + p.negateSelf(); + } + + /** + * Negate a fraction. + * @param f fraction to negate + * @return a new fraction which is the opposite of f + */ + public static PolynomialFraction negate(PolynomialFraction f) { + PolynomialFraction copy = new PolynomialFraction(f); + copy.negateSelf(); + return copy; + } + + /** + * Add a fraction to the instance. + * @param f fraction to add. + */ + public void addToSelf(PolynomialFraction f) { + PolynomialFraction sum = add(this, f); + p = sum.p; + q = sum.q; + } + + /** Add two fractions. + * @param f1 first fraction + * @param f2 second fraction + * @return a new fraction which is the sum of f1 and f2 + */ + public static PolynomialFraction add(PolynomialFraction f1, + PolynomialFraction f2) { + Polynomial.Rational num = + Polynomial.Rational.add(Polynomial.Rational.multiply(f1.p, f2.q), + Polynomial.Rational.multiply(f2.p, f1.q)); + Polynomial.Rational den = Polynomial.Rational.multiply(f1.q, f2.q); + return new PolynomialFraction(num, den); + } + + /** + * Subtract a fraction to the instance. + * @param f fraction to subtract. + */ + public void subtractFromSelf(PolynomialFraction f) { + PolynomialFraction diff = subtract(this, f); + p = diff.p; + q = diff.q; + } + + /** Subtract two fractions. + * @param f1 first fraction + * @param f2 second fraction + * @return a new fraction which is the difference f1 minus f2 + */ + public static PolynomialFraction subtract(PolynomialFraction f1, + PolynomialFraction f2) { + Polynomial.Rational num = + Polynomial.Rational.subtract(Polynomial.Rational.multiply(f1.p, f2.q), + Polynomial.Rational.multiply(f2.p, f1.q)); + Polynomial.Rational den = Polynomial.Rational.multiply(f1.q, f2.q); + return new PolynomialFraction(num, den); + } + + /** Multiply the instance by a fraction. + * @param f fraction to multiply by + */ + public void multiplySelf(PolynomialFraction f) { + p.multiplySelf(f.p); + q.multiplySelf(f.q); + simplify(); + } + + /** Multiply two fractions. + * @param f1 first fraction + * @param f2 second fraction + * @return a new fraction which is the product of f1 and f2 + */ + public static PolynomialFraction multiply(PolynomialFraction f1, + PolynomialFraction f2) { + PolynomialFraction copy = new PolynomialFraction(f1); + copy.multiplySelf(f2); + return copy; + } + + /** Divide the instance by a fraction. + * @param f fraction to divide by + * @exception ArithmeticException if f is null + */ + public void divideSelf(PolynomialFraction f) { + + if (f.p.isZero()) { + throw new ArithmeticException("divide by zero"); + } + + p.multiplySelf(f.q); + q.multiplySelf(f.p); + + RationalNumber[] a = q.getCoefficients(); + if (a[a.length - 1].isNegative()) { + p.negateSelf(); + q.negateSelf(); + } + + simplify(); + + } + + /** Divide two fractions. + * @param f1 first fraction + * @param f2 second fraction + * @return a new fraction which is the quotient of f1 by f2 + */ + public static PolynomialFraction divide(PolynomialFraction f1, + PolynomialFraction f2) { + PolynomialFraction copy = new PolynomialFraction(f1); + copy.divideSelf(f2); + return copy; + } + + /** Invert the instance. + * Replace the instance by its inverse. + * @exception ArithmeticException if the instance is null + */ + public void invertSelf() { + + if (p.isZero()) { + throw new ArithmeticException("divide by zero"); + } + + Polynomial.Rational tmp = p; + p = q; + q = tmp; + + RationalNumber[] a = q.getCoefficients(); + if (a[a.length - 1].isNegative()) { + p.negateSelf(); + q.negateSelf(); + } + + simplify(); + + } + + /** Invert a fraction. + * @param f fraction to invert + * @return a new fraction which is the inverse of f + */ + public static PolynomialFraction invert(PolynomialFraction f) { + PolynomialFraction copy = new PolynomialFraction(f); + copy.invertSelf(); + return copy; + } + + /** Simplify a fraction. + * If the denominator polynom is a constant polynom, then + * simplification involves merging this constant in the rational + * coefficients of the numerator in order to replace the denominator + * by the constant 1. If the degree of the denominator is non null, + * then simplification involves both removing common polynomial + * factors (by euclidian division) and replacing rational + * coefficients by integer coefficients (multiplying both numerator + * and denominator by the proper value). The signs of both the + * numerator and the denominator are adjusted in order to have a + * positive leeding degree term in the denominator. + */ + private void simplify() { + + Polynomial.Rational a = new Polynomial.Rational(p); + Polynomial.Rational b = new Polynomial.Rational(q); + if (a.getDegree() < b.getDegree()) { + Polynomial.Rational tmp = a; + a = b; + b = tmp; + } + + Polynomial.DivisionResult res = + Polynomial.Rational.euclidianDivision(a, b); + while (res.remainder.getDegree() != 0) { + a = b; + b = res.remainder; + res = Polynomial.Rational.euclidianDivision(a, b); + } + + if (res.remainder.isZero()) { + // there is a common factor we can remove + p = Polynomial.Rational.euclidianDivision(p, b).quotient; + q = Polynomial.Rational.euclidianDivision(q, b).quotient; + } + + if (q.getDegree() == 0) { + if (! q.isOne()) { + RationalNumber f = q.getCoefficients()[0]; + f.invertSelf(); + p.multiplySelf(f); + q = new Polynomial.Rational(1l); + } + } else { + + BigInteger lcm = p.getDenominatorsLCM(); + if (lcm.compareTo(BigInteger.ONE) != 0) { + p.multiplySelf(lcm); + q.multiplySelf(lcm); + } + + lcm = q.getDenominatorsLCM(); + if (lcm.compareTo(BigInteger.ONE) != 0) { + p.multiplySelf(lcm); + q.multiplySelf(lcm); + } + + } + + if (q.getCoefficients()[q.getDegree()].isNegative()) { + p.negateSelf(); + q.negateSelf(); + } + + } + + /** + * Get the numerator. + * @return the numerator + */ + public Polynomial.Rational getNumerator() { + return p; + } + + /** + * Get the denominator. + * @return the denominator (leeding coefficient is always positive) + */ + public Polynomial.Rational getDenominator() { + return q; + } + + /** Set the name of the unknown (to appear during conversions to + * strings). + * @param name name to set (if null, the default 'x' value will be + * used) + */ + public void setUnknownName(String name) { + p.setUnknownName(name); + q.setUnknownName(name); + } + + public String toString() { + if (p.isZero()) { + return "0"; + } else if (q.isOne()) { + return p.toString(); + } else { + + StringBuffer s = new StringBuffer(); + + String pString = p.toString(); + if (pString.indexOf(' ') > 0) { + s.append('('); + s.append(pString); + s.append(')'); + } else { + s.append(pString); + } + + s.append('/'); + + String qString = q.toString(); + if (qString.indexOf(' ') > 0) { + s.append('('); + s.append(qString); + s.append(')'); + } else { + s.append(qString); + } + + return s.toString(); + + } + } + + /** Numerator. */ + private Polynomial.Rational p; + + /** Denominator. */ + private Polynomial.Rational q; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/algebra/RationalNumber.java b/src/mantissa/src/org/spaceroots/mantissa/algebra/RationalNumber.java new file mode 100644 index 000000000..711cec4c5 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/algebra/RationalNumber.java @@ -0,0 +1,536 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.algebra; + +import java.math.BigInteger; +/** + * This class implements reduced rational numbers. + + * @version $Id: RationalNumber.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class RationalNumber { + + /** + * Simple constructor. + * Build a null rational number + */ + public RationalNumber() { + p = BigInteger.ZERO; + q = BigInteger.ONE; + } + + /** + * Simple constructor. + * Build a rational number from a numerator and a denominator. + * @param numerator numerator of the rational number + * @param denominator denominator of the rational number + * @exception ArithmeticException if the denominator is zero + */ + public RationalNumber(long numerator, long denominator) { + reset(numerator, denominator); + } + + /** + * Simple constructor. + * Build a rational number from a numerator and a denominator. + * @param numerator numerator of the rational number + * @param denominator denominator of the rational number + * @exception ArithmeticException if the denominator is zero + */ + public RationalNumber(BigInteger numerator, BigInteger denominator) { + reset(numerator, denominator); + } + + /** + * Simple constructor. + * Build a rational number from a single integer + * @param l value of the rational number + */ + public RationalNumber(long l) { + p = BigInteger.valueOf(l); + q = BigInteger.ONE; + } + + /** + * Simple constructor. + * Build a rational number from a single integer + * @param i value of the rational number + */ + public RationalNumber(BigInteger i) { + p = i; + q = BigInteger.ONE; + } + + /** + * Copy-constructor. + * @param r rational number to copy + */ + public RationalNumber(RationalNumber r) { + p = r.p; + q = r.q; + } + + /** Reset the instance from a numerator and a denominator. + * @param numerator numerator of the rational number + * @param denominator denominator of the rational number + * @exception ArithmeticException if the denominator is zero + */ + public void reset(long numerator, long denominator) { + if (denominator == 0l) { + throw new ArithmeticException("divide by zero"); + } + + p = BigInteger.valueOf(numerator); + q = BigInteger.valueOf(denominator); + + if (q.signum() < 0) { + p = p.negate(); + q = q.negate(); + } + + simplify(); + + } + + /** Reset the instance from a numerator and a denominator. + * @param numerator numerator of the rational number + * @param denominator denominator of the rational number + * @exception ArithmeticException if the denominator is zero + */ + public void reset(BigInteger numerator, BigInteger denominator) { + if (denominator.signum() == 0) { + throw new ArithmeticException("divide by zero"); + } + + p = numerator; + q = denominator; + + if (q.signum() < 0) { + p = p.negate(); + q = q.negate(); + } + + simplify(); + + } + + /** Reset the instance from a single integer + * @param l value of the rational number + */ + public void reset(long l) { + p = BigInteger.valueOf(l); + q = BigInteger.ONE; + } + + /** Reset the instance from a single integer + * @param i value of the rational number + */ + public void reset(BigInteger i) { + p = i; + q = BigInteger.ONE; + } + + /** Reset the instance from another rational number. + * @param r rational number to copy + */ + public void reset(RationalNumber r) { + p = r.p; + q = r.q; + } + + /** + * Negate the instance + */ + public void negateSelf() { + p = p.negate(); + } + + /** + * Negate a rational number. + * @param r rational number to negate + * @return a new rational number which is the opposite of r + */ + public static RationalNumber negate(RationalNumber r) { + RationalNumber copy = new RationalNumber(r); + copy.negateSelf(); + return copy; + } + + /** + * Add a rational number to the instance. + * @param r rational number to add. + */ + public void addToSelf(RationalNumber r) { + p = p.multiply(r.q).add(r.p.multiply(q)); + q = q.multiply(r.q); + simplify(); + } + + /** Add two rational numbers. + * @param r1 first rational number + * @param r2 second rational number + * @return a new rational number which is the sum of r1 and r2 + */ + public static RationalNumber add(RationalNumber r1, RationalNumber r2) { + return new RationalNumber(r1.p.multiply(r2.q).add(r2.p.multiply(r1.q)), + r1.q.multiply(r2.q)); + } + + /** + * Subtract a rational number to the instance. + * @param r rational number to subtract. + */ + public void subtractFromSelf(RationalNumber r) { + p = p.multiply(r.q).subtract(r.p.multiply(q)); + q = q.multiply(r.q); + simplify(); + } + + /** Subtract two rational numbers. + * @param r1 first rational number + * @param r2 second rational number + * @return a new rational number which is the difference r1 minus r2 + */ + public static RationalNumber subtract(RationalNumber r1, RationalNumber r2) { + return new RationalNumber(r1.p.multiply(r2.q).subtract(r2.p.multiply(r1.q)), + r1.q.multiply(r2.q)); + } + + /** Multiply the instance by an integer. + * @param l integer to multiply by + */ + public void multiplySelf(long l) { + p = p.multiply(BigInteger.valueOf(l)); + simplify(); + } + + /** Multiply the instance by an integer. + * @param i integer to multiply by + */ + public void multiplySelf(BigInteger i) { + p = p.multiply(i); + simplify(); + } + + /** Multiply a rational number by an integer. + * @param l integer to multiply by + */ + public static RationalNumber multiply(RationalNumber r, long l) { + return new RationalNumber(r.p.multiply(BigInteger.valueOf(l)), r.q); + } + + /** Multiply a rational number by an integer. + * @param i integer to multiply by + */ + public static RationalNumber multiply(RationalNumber r, BigInteger i) { + return new RationalNumber(r.p.multiply(i), r.q); + } + + /** Multiply the instance by a rational number. + * @param r rational number to multiply by + */ + public void multiplySelf(RationalNumber r) { + p = p.multiply(r.p); + q = q.multiply(r.q); + simplify(); + } + + /** Multiply two rational numbers. + * @param r1 first rational number + * @param r2 second rational number + * @return a new rational number which is the product of r1 and r2 + */ + public static RationalNumber multiply(RationalNumber r1, RationalNumber r2) { + return new RationalNumber(r1.p.multiply(r2.p), + r1.q.multiply(r2.q)); + } + + /** Divide the instance by an integer. + * @param l integer to divide by + * @exception ArithmeticException if l is zero + */ + public void divideSelf(long l) { + + if (l == 0l) { + throw new ArithmeticException("divide by zero"); + } else if (l > 0l) { + q = q.multiply(BigInteger.valueOf(l)); + } else { + p = p.negate(); + q = q.multiply(BigInteger.valueOf(-l)); + } + + simplify(); + + } + + /** Divide the instance by an integer. + * @param i integer to divide by + * @exception ArithmeticException if l is zero + */ + public void divideSelf(BigInteger i) { + + if (i.signum() == 0) { + throw new ArithmeticException("divide by zero"); + } else if (i.signum() > 0) { + q = q.multiply(i); + } else { + p = p.negate(); + q = q.multiply(i.negate()); + } + + simplify(); + + } + + /** Divide a rational number by an integer + * @param r rational number + * @param l integer + * @return a new rational number which is the quotient of r by l + * @exception ArithmeticException if l is zero + */ + public static RationalNumber divide(RationalNumber r, long l) { + RationalNumber copy = new RationalNumber(r); + copy.divideSelf(l); + return copy; + } + + /** Divide a rational number by an integer + * @param r rational number + * @param i integer + * @return a new rational number which is the quotient of r by l + * @exception ArithmeticException if l is zero + */ + public static RationalNumber divide(RationalNumber r, BigInteger i) { + RationalNumber copy = new RationalNumber(r); + copy.divideSelf(i); + return copy; + } + + /** Divide the instance by a rational number. + * @param r rational number to divide by + * @exception ArithmeticException if r is zero + */ + public void divideSelf(RationalNumber r) { + + if (r.p.signum() == 0) { + throw new ArithmeticException("divide by zero"); + } + + p = p.multiply(r.q); + q = q.multiply(r.p); + + if (q.signum() < 0) { + p = p.negate(); + q = q.negate(); + } + + simplify(); + + } + + /** Divide two rational numbers. + * @param r1 first rational number + * @param r2 second rational number + * @return a new rational number which is the quotient of r1 by r2 + * @exception ArithmeticException if r2 is zero + */ + public static RationalNumber divide(RationalNumber r1, RationalNumber r2) { + RationalNumber copy = new RationalNumber(r1); + copy.divideSelf(r2); + return copy; + } + + /** Invert the instance. + * Replace the instance by its inverse. + * @exception ArithmeticException if the instance is zero + */ + public void invertSelf() { + + if (p.signum() == 0) { + throw new ArithmeticException("divide by zero"); + } + + BigInteger tmp = p; + p = q; + q = tmp; + + if (q.signum() < 0) { + p = p.negate(); + q = q.negate(); + } + + } + + /** Invert a rational number. + * @param r rational number to invert + * @return a new rational number which is the inverse of r + * @exception ArithmeticException if r is zero + */ + public static RationalNumber invert(RationalNumber r) { + return new RationalNumber(r.q, r.p); + } + + /** + * Add the product of two rational numbers to the instance. + * This operation is equivalent to + * addToSelf(RationalNumber.multiply(r1, r2)) except + * that no intermediate simplification is attempted. + * @param r1 first term of the product to add + * @param r2 second term of the product to add + */ + public void multiplyAndAddToSelf(RationalNumber r1, RationalNumber r2) { + BigInteger r1qr2q = r1.q.multiply(r2.q); + p = p.multiply(r1qr2q).add(r1.p.multiply(r2.p).multiply(q)); + q = q.multiply(r1qr2q); + simplify(); + } + + /** + * Subtract the product of two rational numbers from the instance. + * This operation is equivalent to + * subtractFromSelf(RationalNumber.multiply(r1, r2)) + * except that no intermediate simplification is attempted. + * @param r1 first term of the product to subtract + * @param r2 second term of the product to subtract + */ + public void multiplyAndSubtractFromSelf(RationalNumber r1, RationalNumber r2) { + BigInteger r1qr2q = r1.q.multiply(r2.q); + p = p.multiply(r1qr2q).subtract(r1.p.multiply(r2.p).multiply(q)); + q = q.multiply(r1qr2q); + simplify(); + } + + /** Simplify a rational number by removing common factors. + */ + private void simplify() { + if (p.signum() == 0) { + q = BigInteger.ONE; + } else { + BigInteger gcd = p.gcd(q); + p = p.divide(gcd); + q = q.divide(gcd); + } + } + + /** + * Get the numerator. + * @return the signed numerator + */ + public BigInteger getNumerator() { + return p; + } + + /** + * Get the denominator. + * @return the denominator (always positive) + */ + public BigInteger getDenominator() { + return q; + } + + /** Check if the number is zero. + * @return true if the number is zero + */ + public boolean isZero() { + return p.signum() == 0; + } + + /** Check if the number is one. + * @return true if the number is one + */ + public boolean isOne() { + return (p.compareTo(BigInteger.ONE) == 0) + && (q.compareTo(BigInteger.ONE) == 0); + } + + /** Check if the number is integer. + * @return true if the number is an integer + */ + public boolean isInteger() { + return q.compareTo(BigInteger.ONE) == 0; + } + + /** Check if the number is negative. + * @return true if the number is negative + */ + public boolean isNegative() { + return p.signum() < 0; + } + + /** Get the absolute value of a rational number. + * @param r rational number from which we want the absolute value + * @return a new rational number which is the absolute value of r + */ + public static RationalNumber abs(RationalNumber r) { + return new RationalNumber(r.p.abs(), r.q); + } + + /** Return the double value of the instance. + * @return the double value of the instance + */ + public double doubleValue() { + BigInteger[] result = p.divideAndRemainder(q); + return result[0].doubleValue() + + (result[1].doubleValue() / q.doubleValue()); + } + + /** Check if the instance is equal to another rational number. + * Equality here is having the same value. + * @return true if the object is a rational number which has the + * same value as the instance + */ + public boolean equals(Object o) { + if (o instanceof RationalNumber) { + RationalNumber r = (RationalNumber) o; + return (p.compareTo(r.p) == 0) && (q.compareTo(r.q) == 0); + } + return false; + } + + /** Returns a hash code value for the object. + * The hash code value is computed from the reduced numerator and + * denominator, hence equal rational numbers have the same hash code, + * as required by the method specification. + * @return a hash code value for this object. + */ + public int hashCode() { + return p.hashCode() ^ q.hashCode(); + } + + /** Returns a string representation of the rational number. + * The representation is reduced: there is no common factor left + * between the numerator and the denominator. The '/' character and + * the denominator are displayed only if the denominator is not + * one. The sign is on the numerator. + * @return string representation of the rational number + */ + public String toString() { + return p + ((q.compareTo(BigInteger.ONE) == 0) ? "" : ("/" + q)); + } + + /** Numerator. */ + private BigInteger p; + + /** Denominator. */ + private BigInteger q; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/estimation/EstimatedParameter.java b/src/mantissa/src/org/spaceroots/mantissa/estimation/EstimatedParameter.java new file mode 100644 index 000000000..a219d9f7d --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/estimation/EstimatedParameter.java @@ -0,0 +1,123 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.estimation; + +import java.io.Serializable; + +/** This class represent the estimated parameters of an estimation problem. + + *

The parameters of an estimation problem have a name, a value and + * a bound flag. The value of bound parameters is considered trusted + * and the solvers should not adjust them. On the other hand, the + * solvers should adjust the value of unbounds parameters until they + * satisfy convergence criterions specific to each solver.

+ + * @version $Id: EstimatedParameter.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class EstimatedParameter + implements Serializable { + + /** Simple constructor. + * Build an instance from a first estimate of the parameter, + * initially considered unbound. + * @param name name of the parameter + * @param firstEstimate first estimate of the parameter + */ + public EstimatedParameter(String name, double firstEstimate) { + this.name = name; + estimate = firstEstimate; + bound = false; + } + + /** Simple constructor. + * Build an instance from a first estimate of the parameter and a + * bound flag + * @param name name of the parameter + * @param firstEstimate first estimate of the parameter + * @param bound flag, should be true if the parameter is bound + */ + public EstimatedParameter(String name, + double firstEstimate, + boolean bound) { + this.name = name; + estimate = firstEstimate; + this.bound = bound; + } + + /** Copy constructor. + * Build a copy of a parameter + * @param parameter instance to copy + */ + public EstimatedParameter(EstimatedParameter parameter) { + name = parameter.name; + estimate = parameter.estimate; + bound = parameter.bound; + } + + /** Set a new estimated value for the parameter. + * @param estimate new estimate for the parameter + */ + public void setEstimate(double estimate) { + this.estimate = estimate; + } + + /** Get the current estimate of the parameter + * @return current estimate + */ + public double getEstimate() { + return estimate; + } + + /** get the name of the parameter + * @return parameter name + */ + public String getName() { + return name; + } + + /** Set the bound flag of the parameter + * @param bound this flag should be set to true if the parameter is + * bound (i.e. if it should not be adjusted by the solver). + */ + public void setBound(boolean bound) { + this.bound = bound; + } + + /** Check if the parameter is bound + * @return true if the parameter is bound */ + public boolean isBound() { + return bound; + } + + /** Name of the parameter */ + private String name; + + /** Current value of the parameter */ + protected double estimate; + + /** Indicator for bound parameters + * (ie parameters that should not be estimated) + */ + private boolean bound; + + private static final long serialVersionUID = -555440800213416949L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/estimation/EstimationException.java b/src/mantissa/src/org/spaceroots/mantissa/estimation/EstimationException.java new file mode 100644 index 000000000..cf36df248 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/estimation/EstimationException.java @@ -0,0 +1,59 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.estimation; + +import org.spaceroots.mantissa.MantissaException; + +/** This class represents exceptions thrown by the estimation solvers. + + * @version $Id: EstimationException.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class EstimationException + extends MantissaException { + + /** Simple constructor. + * Build an exception by translating the specified message + * @param message message to translate + */ + public EstimationException(String message) { + super(message); + } + + /** Simple constructor. + * Build an exception by translating and formating a message + * @param specifier format specifier (to be translated) + * @param parts to insert in the format (no translation) + */ + public EstimationException(String specifier, String[] parts) { + super(specifier, parts); + } + + /** Simple constructor. + * Build an exception from a cause + * @param cause cause of this exception + */ + public EstimationException(Throwable cause) { + super(cause); + } + + private static final long serialVersionUID = 1613719630569355278L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/estimation/EstimationProblem.java b/src/mantissa/src/org/spaceroots/mantissa/estimation/EstimationProblem.java new file mode 100644 index 000000000..13cefe6c3 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/estimation/EstimationProblem.java @@ -0,0 +1,61 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.estimation; + +/** This interface represents an estimation problem. + + *

This interface should be implemented by all real estimation + * problems before they can be handled by the estimators through the + * {@link Estimator#estimate Estimator.estimate} method.

+ + *

An estimation problem, as seen by a solver is a set of + * parameters and a set of measurements. The parameters are adjusted + * during the estimation through the {@link #getUnboundParameters + * getUnboundParameters} and {@link EstimatedParameter#setEstimate + * EstimatedParameter.setEstimate} methods. The measurements both have + * a measured value which is generally fixed at construction and a + * theoretical value which depends on the model and hence varies as + * the parameters are adjusted. The purpose of the solver is to reduce + * the residual between these values, it can retrieve the measurements + * through the {@link #getMeasurements getMeasurements} method.

+ + * @see Estimator + * @see WeightedMeasurement + + * @version $Id: EstimationProblem.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public interface EstimationProblem { + /** Get the measurements of an estimation problem. + * @return measurements + */ + public WeightedMeasurement[] getMeasurements(); + + /** Get the unbound parameters of the problem. + * @return unbound parameters + */ + public EstimatedParameter[] getUnboundParameters(); + + /** Get all the parameters of the problem. + * @return parameters + */ + public EstimatedParameter[] getAllParameters(); + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/estimation/Estimator.java b/src/mantissa/src/org/spaceroots/mantissa/estimation/Estimator.java new file mode 100644 index 000000000..8410f16f6 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/estimation/Estimator.java @@ -0,0 +1,66 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.estimation; + +/** This interface represents solvers for estimation problems. + + *

The classes which are devoted to solve estimation problems + * should implement this interface. The problems which can be handled + * should implement the {@link EstimationProblem} interface which + * gather all the information needed by the solver.

+ + *

The interface is composed only of the {@link #estimate estimate} + * method.

+ + * @see EstimationProblem + + * @version $Id: Estimator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public interface Estimator { + + /** Solve an estimation problem. + + *

The method should set the parameters of the problem to several + * trial values until it reaches convergence. If this method returns + * normally (i.e. without throwing an exception), then the best + * estimate of the parameters can be retrieved from the problem + * itself, through the {@link EstimationProblem#getAllParameters + * EstimationProblem.getAllParameters} method.

+ + * @param problem estimation problem to solve + * @exception EstimationException if the problem cannot be solved + + */ + public void estimate(EstimationProblem problem) + throws EstimationException; + + /** Get the Root Mean Square value. + * Get the Root Mean Square value, i.e. the root of the arithmetic + * mean of the square of all weighted residuals. This is related to the + * criterion that is minimized by the estimator as follows: if + * c if the criterion, and n is the number of + * measurements, the the RMS is sqrt (c/n). + * @param problem estimation problem + * @return RMS value + */ + public double getRMS(EstimationProblem problem); + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/estimation/GaussNewtonEstimator.java b/src/mantissa/src/org/spaceroots/mantissa/estimation/GaussNewtonEstimator.java new file mode 100644 index 000000000..bd53894ea --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/estimation/GaussNewtonEstimator.java @@ -0,0 +1,230 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.estimation; + +import java.io.Serializable; + +import org.spaceroots.mantissa.linalg.Matrix; +import org.spaceroots.mantissa.linalg.GeneralMatrix; +import org.spaceroots.mantissa.linalg.SymetricalMatrix; +import org.spaceroots.mantissa.linalg.SingularMatrixException; + +/** This class implements a solver for estimation problems. + + *

This class solves estimation problems using a weighted least + * squares criterion on the measurement residuals. It uses a + * Gauss-Newton algorithm.

+ + * @version $Id: GaussNewtonEstimator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class GaussNewtonEstimator + implements Estimator, Serializable { + + /** Simple constructor. + + *

This constructor build an estimator and store its convergence + * characteristics.

+ + *

An estimator is considered to have converged whenever either + * the criterion goes below a physical threshold under which + * improvements are considered useless or when the algorithm is + * unable to improve it (even if it is still high). The first + * condition that is met stops the iterations.

+ + *

The fact an estimator has converged does not mean that the + * model accurately fits the measurements. It only means no better + * solution can be found, it does not mean this one is good. Such an + * analysis is left to the caller.

+ + *

If neither conditions are fulfilled before a given number of + * iterations, the algorithm is considered to have failed and an + * {@link EstimationException} is thrown.

+ + * @param maxIterations maximum number of iterations allowed + * @param convergence criterion threshold below which we do not need + * to improve the criterion anymore + * @param steadyStateThreshold steady state detection threshold, the + * problem has converged has reached a steady state if + * Math.abs (Jn - Jn-1) < Jn * convergence, where + * Jn and Jn-1 are the current and + * preceding criterion value (square sum of the weighted residuals + * of considered measurements). + * @param epsilon threshold under which the matrix of the linearized + * problem is considered singular (see {@link + * org.spaceroots.mantissa.linalg.SquareMatrix#solve(Matrix,double) + * SquareMatrix.solve}). */ + public GaussNewtonEstimator(int maxIterations, + double convergence, + double steadyStateThreshold, + double epsilon) { + this.maxIterations = maxIterations; + this.steadyStateThreshold = steadyStateThreshold; + this.convergence = convergence; + this.epsilon = epsilon; + } + + /** Solve an estimation problem using a least squares criterion. + + *

This method set the unbound parameters of the given problem + * starting from their current values through several iterations. At + * each step, the unbound parameters are changed in order to + * minimize a weighted least square criterion based on the + * measurements of the problem.

+ + *

The iterations are stopped either when the criterion goes + * below a physical threshold under which improvement are considered + * useless or when the algorithm is unable to improve it (even if it + * is still high). The first condition that is met stops the + * iterations. If the convergence it nos reached before the maximum + * number of iterations, an {@link EstimationException} is + * thrown.

+ + * @param problem estimation problem to solve + * @exception EstimationException if the problem cannot be solved + + * @see EstimationProblem + + */ + public void estimate(EstimationProblem problem) + throws EstimationException { + int iterations = 0; + double previous = 0.0; + double current = 0.0; + + // iterate until convergence is reached + do { + + if (++iterations > maxIterations) { + throw new EstimationException ("unable to converge in {0} iterations", + new String[] { + Integer.toString(maxIterations) + }); + } + + // perform one iteration + linearEstimate(problem); + + previous = current; + current = evaluateCriterion(problem); + + } while ((iterations < 2) + || (Math.abs(previous - current) > (current * steadyStateThreshold) + && (Math.abs(current) > convergence))); + + } + + /** Estimate the solution of a linear least square problem. + + *

The Gauss-Newton algorithm is iterative. Each iteration + * consist in solving a linearized least square problem. Several + * iterations are needed for general problems since the + * linearization is only an approximation of the problem + * behaviour. However, for linear problems one iteration is enough + * to get the solution. This method is provided in the public + * interface in order to handle more efficiently these linear + * problems.

+ + * @param problem estimation problem to solve + * @exception EstimationException if the problem cannot be solved + + */ + public void linearEstimate(EstimationProblem problem) + throws EstimationException { + + EstimatedParameter[] parameters = problem.getUnboundParameters(); + WeightedMeasurement[] measurements = problem.getMeasurements(); + + // build the linear problem + GeneralMatrix b = new GeneralMatrix(parameters.length, 1); + SymetricalMatrix a = new SymetricalMatrix(parameters.length); + for (int i = 0; i < measurements.length; ++i) { + if (! measurements [i].isIgnored()) { + double weight = measurements[i].getWeight(); + double residual = measurements[i].getResidual(); + + // compute the normal equation + double[] grad = new double[parameters.length]; + Matrix bDecrement = new GeneralMatrix(parameters.length, 1); + for (int j = 0; j < parameters.length; ++j) { + grad[j] = measurements[i].getPartial(parameters[j]); + bDecrement.setElement(j, 0, weight * residual * grad[j]); + } + + // update the matrices + a.selfAddWAAt(weight, grad); + b.selfAdd(bDecrement); + + } + } + + try { + + // solve the linearized least squares problem + Matrix dX = a.solve(b, epsilon); + + // update the estimated parameters + for (int i = 0; i < parameters.length; ++i) { + parameters[i].setEstimate(parameters[i].getEstimate() + + dX.getElement(i, 0)); + } + + } catch(SingularMatrixException e) { + throw new EstimationException(e); + } + + } + + private double evaluateCriterion(EstimationProblem problem) { + double criterion = 0.0; + WeightedMeasurement[] measurements = problem.getMeasurements(); + + for (int i = 0; i < measurements.length; ++i) { + double residual = measurements[i].getResidual(); + criterion += measurements[i].getWeight() * residual * residual; + } + + return criterion; + + } + + /** Get the Root Mean Square value. + * Get the Root Mean Square value, i.e. the root of the arithmetic + * mean of the square of all weighted residuals. This is related to the + * criterion that is minimized by the estimator as follows: if + * c if the criterion, and n is the number of + * measurements, then the RMS is sqrt (c/n). + * @param problem estimation problem + * @return RMS value + */ + public double getRMS(EstimationProblem problem) { + double criterion = evaluateCriterion(problem); + int n = problem.getMeasurements().length; + return Math.sqrt(criterion / n); + } + + private int maxIterations; + private double steadyStateThreshold; + private double convergence; + private double epsilon; + + private static final long serialVersionUID = -7606628156644194170L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/estimation/LeastSquaresEstimator.java b/src/mantissa/src/org/spaceroots/mantissa/estimation/LeastSquaresEstimator.java new file mode 100644 index 000000000..3b3e4de9a --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/estimation/LeastSquaresEstimator.java @@ -0,0 +1,73 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.estimation; + +import java.io.Serializable; + +/** This class implements a solver for estimation problems. + * @deprecated this class has been replaced by the {@link + * org.spaceroots.mantissa.estimation.GaussNewtonEstimator GaussNewtonEstimator} + * class. It is now a simple wrapper delegating everything to {@link + * org.spaceroots.mantissa.estimation.GaussNewtonEstimator GaussNewtonEstimator} + * @version $Id: LeastSquaresEstimator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + */ +public class LeastSquaresEstimator implements Estimator, Serializable { + + /** Simple constructor. + * @see org.spaceroots.mantissa.estimation.GaussNewtonEstimator#GaussNewtonEstimator(int, + * double, double, double) + */ + public LeastSquaresEstimator(int maxIterations, + double convergence, + double steadyStateThreshold, + double epsilon) { + estimator = new GaussNewtonEstimator(maxIterations, + convergence, + steadyStateThreshold, + epsilon); + } + + /** Solve an estimation problem using a least squares criterion. + * @see org.spaceroots.mantissa.estimation.GaussNewtonEstimator#estimate + */ + public void estimate(EstimationProblem problem) + throws EstimationException { + estimator.estimate(problem); + } + + /** Estimate the solution of a linear least square problem. + * @see org.spaceroots.mantissa.estimation.GaussNewtonEstimator#linearEstimate + */ + public void linearEstimate(EstimationProblem problem) + throws EstimationException { + estimator.linearEstimate(problem); + } + + /** Get the Root Mean Square value. + * @see org.spaceroots.mantissa.estimation.GaussNewtonEstimator#getRMS + */ + public double getRMS(EstimationProblem problem) { + return estimator.getRMS(problem); + } + + private GaussNewtonEstimator estimator; + + private static final long serialVersionUID = -7542643494637247770L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/estimation/LevenbergMarquardtEstimator.java b/src/mantissa/src/org/spaceroots/mantissa/estimation/LevenbergMarquardtEstimator.java new file mode 100644 index 000000000..63af4e32e --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/estimation/LevenbergMarquardtEstimator.java @@ -0,0 +1,972 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.estimation; + +import java.io.Serializable; +import java.util.Arrays; + +/** This class solves a least squares problem. + + *

This implementation should work even for over-determined systems + * (i.e. systems having more variables than equations). Over-determined systems + * are solved by ignoring the variables which have the smallest impact according + * to their jacobian column norm. Only the rank of the matrix and some loop bounds + * are changed to implement this. This feature has undergone only basic testing + * for now and should still be considered experimental.

+ + *

The resolution engine is a simple translation of the MINPACK lmder routine with minor + * changes. The changes include the over-determined resolution and the Q.R. + * decomposition which has been rewritten following the algorithm described in the + * P. Lascaux and R. Theodor book Analyse numérique matricielle + * appliquée à l'art de l'ingénieur, Masson 1986. The + * redistribution policy for MINPACK is available here, for convenience, it + * is reproduced below.

+ + * + * + * + *
+ * Minpack Copyright Notice (1999) University of Chicago. + * All rights reserved + *
+ * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + *
    + *
  1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer.
  2. + *
  3. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution.
  4. + *
  5. The end-user documentation included with the redistribution, if any, + * must include the following acknowledgment: + * This product includes software developed by the University of + * Chicago, as Operator of Argonne National Laboratory. + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear.
  6. + *
  7. WARRANTY DISCLAIMER. THE SOFTWARE IS SUPPLIED "AS IS" + * WITHOUT WARRANTY OF ANY KIND. THE COPYRIGHT HOLDER, THE + * UNITED STATES, THE UNITED STATES DEPARTMENT OF ENERGY, AND + * THEIR EMPLOYEES: (1) DISCLAIM ANY WARRANTIES, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO ANY IMPLIED WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE + * OR NON-INFRINGEMENT, (2) DO NOT ASSUME ANY LEGAL LIABILITY + * OR RESPONSIBILITY FOR THE ACCURACY, COMPLETENESS, OR + * USEFULNESS OF THE SOFTWARE, (3) DO NOT REPRESENT THAT USE OF + * THE SOFTWARE WOULD NOT INFRINGE PRIVATELY OWNED RIGHTS, (4) + * DO NOT WARRANT THAT THE SOFTWARE WILL FUNCTION + * UNINTERRUPTED, THAT IT IS ERROR-FREE OR THAT ANY ERRORS WILL + * BE CORRECTED.
  8. + *
  9. LIMITATION OF LIABILITY. IN NO EVENT WILL THE COPYRIGHT + * HOLDER, THE UNITED STATES, THE UNITED STATES DEPARTMENT OF + * ENERGY, OR THEIR EMPLOYEES: BE LIABLE FOR ANY INDIRECT, + * INCIDENTAL, CONSEQUENTIAL, SPECIAL OR PUNITIVE DAMAGES OF + * ANY KIND OR NATURE, INCLUDING BUT NOT LIMITED TO LOSS OF + * PROFITS OR LOSS OF DATA, FOR ANY REASON WHATSOEVER, WHETHER + * SUCH LIABILITY IS ASSERTED ON THE BASIS OF CONTRACT, TORT + * (INCLUDING NEGLIGENCE OR STRICT LIABILITY), OR OTHERWISE, + * EVEN IF ANY OF SAID PARTIES HAS BEEN WARNED OF THE + * POSSIBILITY OF SUCH LOSS OR DAMAGES.
  10. + *
    + + * @author Argonne National Laboratory. MINPACK project. March 1980 (original fortran) + * @author Burton S. Garbow (original fortran) + * @author Kenneth E. Hillstrom (original fortran) + * @author Jorge J. More (original fortran) + * @author Luc Maisonobe (Java translation) + */ +public class LevenbergMarquardtEstimator implements Serializable, Estimator { + + /** Build an estimator for least squares problems. + *

    The default values for the algorithm settings are: + *

    + *

    + */ + public LevenbergMarquardtEstimator() { + // default values for the tuning parameters + setInitialStepBoundFactor(100.0); + setMaxCostEval(1000); + setCostRelativeTolerance(1.0e-10); + setParRelativeTolerance(1.0e-10); + setOrthoTolerance(1.0e-10); + } + + /** Set the positive input variable used in determining the initial step bound. + * This bound is set to the product of initialStepBoundFactor and the euclidean norm of diag*x if nonzero, + * or else to initialStepBoundFactor itself. In most cases factor should lie + * in the interval (0.1, 100.0). 100.0 is a generally recommended value + * @param initialStepBoundFactor initial step bound factor + * @see #estimate + */ + public void setInitialStepBoundFactor(double initialStepBoundFactor) { + this.initialStepBoundFactor = initialStepBoundFactor; + } + + /** Set the maximal number of cost evaluations. + * @param maxCostEval maximal number of cost evaluations + * @see #estimate + */ + public void setMaxCostEval(int maxCostEval) { + this.maxCostEval = maxCostEval; + } + + /** Set the desired relative error in the sum of squares. + * @param costRelativeTolerance desired relative error in the sum of squares + * @see #estimate + */ + public void setCostRelativeTolerance(double costRelativeTolerance) { + this.costRelativeTolerance = costRelativeTolerance; + } + + /** Set the desired relative error in the approximate solution parameters. + * @param parRelativeTolerance desired relative error + * in the approximate solution parameters + * @see #estimate + */ + public void setParRelativeTolerance(double parRelativeTolerance) { + this.parRelativeTolerance = parRelativeTolerance; + } + + /** Set the desired max cosine on the orthogonality. + * @param orthoTolerance desired max cosine on the orthogonality + * between the function vector and the columns of the jacobian + * @see #estimate + */ + public void setOrthoTolerance(double orthoTolerance) { + this.orthoTolerance = orthoTolerance; + } + + /** Get the number of cost evaluations. + * @return number of cost evaluations + * */ + public int getCostEvaluations() { + return costEvaluations; + } + + /** Get the number of jacobian evaluations. + * @return number of jacobian evaluations + * */ + public int getJacobianEvaluations() { + return jacobianEvaluations; + } + + /** Update the jacobian matrix. + */ + private void updateJacobian() { + ++jacobianEvaluations; + Arrays.fill(jacobian, 0); + for (int i = 0, index = 0; i < rows; i++) { + WeightedMeasurement wm = measurements[i]; + double factor = -Math.sqrt(wm.getWeight()); + for (int j = 0; j < cols; ++j) { + jacobian[index++] = factor * wm.getPartial(parameters[j]); + } + } + } + + /** Update the residuals array and cost function value. + */ + private void updateResidualsAndCost() { + ++costEvaluations; + cost = 0; + for (int i = 0, index = 0; i < rows; i++, index += cols) { + WeightedMeasurement wm = measurements[i]; + double residual = wm.getResidual(); + residuals[i] = Math.sqrt(wm.getWeight()) * residual; + cost += wm.getWeight() * residual * residual; + } + cost = Math.sqrt(cost); + } + + /** Get the Root Mean Square value. + * Get the Root Mean Square value, i.e. the root of the arithmetic + * mean of the square of all weighted residuals. This is related to the + * criterion that is minimized by the estimator as follows: if + * c if the criterion, and n is the number of + * measurements, then the RMS is sqrt (c/n). + * @param problem estimation problem + * @return RMS value + */ + public double getRMS(EstimationProblem problem) { + WeightedMeasurement[] wm = problem.getMeasurements(); + double criterion = 0; + for (int i = 0; i < wm.length; ++i) { + double residual = wm[i].getResidual(); + criterion += wm[i].getWeight() * residual * residual; + } + return Math.sqrt(criterion / wm.length); + } + + /** Solve an estimation problem using the Levenberg-Marquardt algorithm. + *

    The algorithm used is a modified Levenberg-Marquardt one, based + * on the MINPACK lmder + * routine. The algorithm settings must have been set up before this method + * is called with the {@link #setInitialStepBoundFactor}, + * {@link #setMaxCostEval}, {@link #setCostRelativeTolerance}, + * {@link #setParRelativeTolerance} and {@link #setOrthoTolerance} methods. + * If these methods have not been called, the default values set up by the + * {@link #LevenbergMarquardtEstimator() constructor} will be used.

    + *

    The authors of the original fortran function are:

    + * + *

    Luc Maisonobe did the Java translation.

    + * @param problem estimation problem to solve + * @exception EstimationException if convergence cannot be + * reached with the specified algorithm settings or if there are more variables + * than equations + * @see #setInitialStepBoundFactor + * @see #setMaxCostEval + * @see #setCostRelativeTolerance + * @see #setParRelativeTolerance + * @see #setOrthoTolerance + */ + public void estimate(EstimationProblem problem) + throws EstimationException { + + // retrieve the equations and the parameters + measurements = problem.getMeasurements(); + parameters = problem.getUnboundParameters(); + + // arrays shared with the other private methods + rows = measurements.length; + cols = parameters.length; + solvedCols = Math.min(rows, cols); + jacobian = new double[rows * cols]; + diagR = new double[cols]; + jacNorm = new double[cols]; + beta = new double[cols]; + permutation = new int[cols]; + lmDir = new double[cols]; + residuals = new double[rows]; + + // local variables + double delta = 0, xNorm = 0; + double[] diag = new double[cols]; + double[] oldX = new double[cols]; + double[] oldRes = new double[rows]; + double[] work1 = new double[cols]; + double[] work2 = new double[cols]; + double[] work3 = new double[cols]; + + // evaluate the function at the starting point and calculate its norm + updateResidualsAndCost(); + + // outer loop + lmPar = 0; + costEvaluations = 0; + jacobianEvaluations = 0; + boolean firstIteration = true; + while (costEvaluations < maxCostEval) { + + // compute the Q.R. decomposition of the jacobian matrix + updateJacobian(); + qrDecomposition(); + + // compute Qt.res + qTy(residuals); + + // now we don't need Q anymore, + // so let jacobian contain the R matrix with its diagonal elements + for (int k = 0; k < solvedCols; ++k) { + int pk = permutation[k]; + jacobian[k * cols + pk] = diagR[pk]; + } + + if (firstIteration) { + + // scale the variables according to the norms of the columns + // of the initial jacobian + xNorm = 0; + for (int k = 0; k < cols; ++k) { + double dk = jacNorm[k]; + if (dk == 0) { + dk = 1.0; + } + double xk = dk * parameters[k].getEstimate(); + xNorm += xk * xk; + diag[k] = dk; + } + xNorm = Math.sqrt(xNorm); + + // initialize the step bound delta + delta = (xNorm == 0) + ? initialStepBoundFactor : (initialStepBoundFactor * xNorm); + + } + + // check orthogonality between function vector and jacobian columns + double maxCosine = 0; + if (cost != 0) { + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + double s = jacNorm[pj]; + if (s != 0) { + double sum = 0; + for (int i = 0, index = pj; i <= j; ++i, index += cols) { + sum += jacobian[index] * residuals[i]; + } + maxCosine = Math.max(maxCosine, Math.abs(sum) / (s * cost)); + } + } + } + if (maxCosine <= orthoTolerance) { + return; + } + + // rescale if necessary + for (int j = 0; j < cols; ++j) { + diag[j] = Math.max(diag[j], jacNorm[j]); + } + + // inner loop + for (double ratio = 0; ratio < 1.0e-4;) { + + // save the state + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + oldX[pj] = parameters[pj].getEstimate(); + } + double previousCost = cost; + double[] tmpVec = residuals; + residuals = oldRes; + oldRes = tmpVec; + + // determine the Levenberg-Marquardt parameter + determineLMParameter(oldRes, delta, diag, work1, work2, work3); + + // compute the new point and the norm of the evolution direction + double lmNorm = 0; + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + lmDir[pj] = -lmDir[pj]; + parameters[pj].setEstimate(oldX[pj] + lmDir[pj]); + double s = diag[pj] * lmDir[pj]; + lmNorm += s * s; + } + lmNorm = Math.sqrt(lmNorm); + + // on the first iteration, adjust the initial step bound. + if (firstIteration) { + delta = Math.min(delta, lmNorm); + } + + // evaluate the function at x + p and calculate its norm + updateResidualsAndCost(); + + // compute the scaled actual reduction + double actRed = -1.0; + if (0.1 * cost < previousCost) { + double r = cost / previousCost; + actRed = 1.0 - r * r; + } + + // compute the scaled predicted reduction + // and the scaled directional derivative + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + double dirJ = lmDir[pj]; + work1[j] = 0; + for (int i = 0, index = pj; i <= j; ++i, index += cols) { + work1[i] += jacobian[index] * dirJ; + } + } + double coeff1 = 0; + for (int j = 0; j < solvedCols; ++j) { + coeff1 += work1[j] * work1[j]; + } + double pc2 = previousCost * previousCost; + coeff1 = coeff1 / pc2; + double coeff2 = lmPar * lmNorm * lmNorm / pc2; + double preRed = coeff1 + 2 * coeff2; + double dirDer = -(coeff1 + coeff2); + + // ratio of the actual to the predicted reduction + ratio = (preRed == 0) ? 0 : (actRed / preRed); + + // update the step bound + if (ratio <= 0.25) { + double tmp = + (actRed < 0) ? (0.5 * dirDer / (dirDer + 0.5 * actRed)) : 0.5; + if ((0.1 * cost >= previousCost) || (tmp < 0.1)) { + tmp = 0.1; + } + delta = tmp * Math.min(delta, 10.0 * lmNorm); + lmPar /= tmp; + } else if ((lmPar == 0) || (ratio >= 0.75)) { + delta = 2 * lmNorm; + lmPar *= 0.5; + } + + // test for successful iteration. + if (ratio >= 1.0e-4) { + // successful iteration, update the norm + firstIteration = false; + xNorm = 0; + for (int k = 0; k < cols; ++k) { + double xK = diag[k] * parameters[k].getEstimate(); + xNorm += xK * xK; + } + xNorm = Math.sqrt(xNorm); + } else { + // failed iteration, reset the previous values + cost = previousCost; + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + parameters[pj].setEstimate(oldX[pj]); + } + tmpVec = residuals; + residuals = oldRes; + oldRes = tmpVec; + } + + // tests for convergence. + if (((Math.abs(actRed) <= costRelativeTolerance) + && (preRed <= costRelativeTolerance) + && (ratio <= 2.0)) + || (delta <= parRelativeTolerance * xNorm)) { + return; + } + + // tests for termination and stringent tolerances + // (2.2204e-16 is the machine epsilon for IEEE754) + if (costEvaluations >= maxCostEval) { + break; + } + if ((Math.abs(actRed) <= 2.2204e-16) + && (preRed <= 2.2204e-16) + && (ratio <= 2.0)) { + throw new EstimationException("cost relative tolerance is too small ({0})," + + " no further reduction in the" + + " sum of squares is possible", + new String[] { + Double.toString(costRelativeTolerance) + }); + } else if (delta <= 2.2204e-16 * xNorm) { + throw new EstimationException("parameters relative tolerance is too small" + + " ({0}), no further improvement in" + + " the approximate solution is possible", + new String[] { + Double.toString(parRelativeTolerance) + }); + } else if (maxCosine <= 2.2204e-16) { + throw new EstimationException("orthogonality tolerance is too small ({0})," + + " solution is orthogonal to the jacobian", + new String[] { + Double.toString(orthoTolerance) + }); + } + + } + + } + + throw new EstimationException("maximal number of evaluations exceeded ({0})", + new String[] { + Integer.toString(maxCostEval) + }); + + } + + /** Determine the Levenberg-Marquardt parameter. + *

    This implementation is a translation in Java of the MINPACK + * lmpar + * routine.

    + *

    This method sets the lmPar and lmDir attributes.

    + *

    The authors of the original fortran function are:

    + * + *

    Luc Maisonobe did the Java translation.

    + * @param qy array containing qTy + * @param delta upper bound on the euclidean norm of diagR * lmDir + * @param diag diagonal matrix + * @param work1 work array + * @param work2 work array + * @param work3 work array + */ + private void determineLMParameter(double[] qy, double delta, double[] diag, + double[] work1, double[] work2, double[] work3) { + + // compute and store in x the gauss-newton direction, if the + // jacobian is rank-deficient, obtain a least squares solution + for (int j = 0; j < rank; ++j) { + lmDir[permutation[j]] = qy[j]; + } + for (int j = rank; j < cols; ++j) { + lmDir[permutation[j]] = 0; + } + for (int k = rank - 1; k >= 0; --k) { + int pk = permutation[k]; + double ypk = lmDir[pk] / diagR[pk]; + for (int i = 0, index = pk; i < k; ++i, index += cols) { + lmDir[permutation[i]] -= ypk * jacobian[index]; + } + lmDir[pk] = ypk; + } + + // evaluate the function at the origin, and test + // for acceptance of the Gauss-Newton direction + double dxNorm = 0; + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + double s = diag[pj] * lmDir[pj]; + work1[pj] = s; + dxNorm += s * s; + } + dxNorm = Math.sqrt(dxNorm); + double fp = dxNorm - delta; + if (fp <= 0.1 * delta) { + lmPar = 0; + return; + } + + // if the jacobian is not rank deficient, the Newton step provides + // a lower bound, parl, for the zero of the function, + // otherwise set this bound to zero + double sum2, parl = 0; + if (rank == solvedCols) { + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + work1[pj] *= diag[pj] / dxNorm; + } + sum2 = 0; + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + double sum = 0; + for (int i = 0, index = pj; i < j; ++i, index += cols) { + sum += jacobian[index] * work1[permutation[i]]; + } + double s = (work1[pj] - sum) / diagR[pj]; + work1[pj] = s; + sum2 += s * s; + } + parl = fp / (delta * sum2); + } + + // calculate an upper bound, paru, for the zero of the function + sum2 = 0; + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + double sum = 0; + for (int i = 0, index = pj; i <= j; ++i, index += cols) { + sum += jacobian[index] * qy[i]; + } + sum /= diag[pj]; + sum2 += sum * sum; + } + double gNorm = Math.sqrt(sum2); + double paru = gNorm / delta; + if (paru == 0) { + // 2.2251e-308 is the smallest positive real for IEE754 + paru = 2.2251e-308 / Math.min(delta, 0.1); + } + + // if the input par lies outside of the interval (parl,paru), + // set par to the closer endpoint + lmPar = Math.min(paru, Math.max(lmPar, parl)); + if (lmPar == 0) { + lmPar = gNorm / dxNorm; + } + + for (int countdown = 10; countdown >= 0; --countdown) { + + // evaluate the function at the current value of lmPar + if (lmPar == 0) { + lmPar = Math.max(2.2251e-308, 0.001 * paru); + } + double sPar = Math.sqrt(lmPar); + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + work1[pj] = sPar * diag[pj]; + } + determineLMDirection(qy, work1, work2, work3); + + dxNorm = 0; + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + double s = diag[pj] * lmDir[pj]; + work3[pj] = s; + dxNorm += s * s; + } + dxNorm = Math.sqrt(dxNorm); + double previousFP = fp; + fp = dxNorm - delta; + + // if the function is small enough, accept the current value + // of lmPar, also test for the exceptional cases where parl is zero + if ((Math.abs(fp) <= 0.1 * delta) + || ((parl == 0) && (fp <= previousFP) && (previousFP < 0))) { + return; + } + + // compute the Newton correction + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + work1[pj] = work3[pj] * diag[pj] / dxNorm; + } + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + work1[pj] /= work2[j]; + double tmp = work1[pj]; + for (int i = j + 1; i < solvedCols; ++i) { + work1[permutation[i]] -= jacobian[i * cols + pj] * tmp; + } + } + sum2 = 0; + for (int j = 0; j < solvedCols; ++j) { + double s = work1[permutation[j]]; + sum2 += s * s; + } + double correction = fp / (delta * sum2); + + // depending on the sign of the function, update parl or paru. + if (fp > 0) { + parl = Math.max(parl, lmPar); + } else if (fp < 0) { + paru = Math.min(paru, lmPar); + } + + // compute an improved estimate for lmPar + lmPar = Math.max(parl, lmPar + correction); + + } + } + + /** Solve a*x = b and d*x = 0 in the least squares sense. + *

    This implementation is a translation in Java of the MINPACK + * qrsolv + * routine.

    + *

    This method sets the lmDir and lmDiag attributes.

    + *

    The authors of the original fortran function are:

    + * + *

    Luc Maisonobe did the Java translation.

    + * @param qy array containing qTy + * @param diag diagonal matrix + * @param lmDiag diagonal elements associated with lmDir + * @param work work array + */ + private void determineLMDirection(double[] qy, double[] diag, + double[] lmDiag, double[] work) { + + // copy R and Qty to preserve input and initialize s + // in particular, save the diagonal elements of R in lmDir + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + for (int i = j + 1; i < solvedCols; ++i) { + jacobian[i * cols + pj] = jacobian[j * cols + permutation[i]]; + } + lmDir[j] = diagR[pj]; + work[j] = qy[j]; + } + + // eliminate the diagonal matrix d using a Givens rotation + for (int j = 0; j < solvedCols; ++j) { + + // prepare the row of d to be eliminated, locating the + // diagonal element using p from the Q.R. factorization + int pj = permutation[j]; + double dpj = diag[pj]; + if (dpj != 0) { + Arrays.fill(lmDiag, j + 1, lmDiag.length, 0); + } + lmDiag[j] = dpj; + + // the transformations to eliminate the row of d + // modify only a single element of Qty + // beyond the first n, which is initially zero. + double qtbpj = 0; + for (int k = j; k < solvedCols; ++k) { + int pk = permutation[k]; + + // determine a Givens rotation which eliminates the + // appropriate element in the current row of d + if (lmDiag[k] != 0) { + + double sin, cos; + double rkk = jacobian[k * cols + pk]; + if (Math.abs(rkk) < Math.abs(lmDiag[k])) { + double cotan = rkk / lmDiag[k]; + sin = 1.0 / Math.sqrt(1.0 + cotan * cotan); + cos = sin * cotan; + } else { + double tan = lmDiag[k] / rkk; + cos = 1.0 / Math.sqrt(1.0 + tan * tan); + sin = cos * tan; + } + + // compute the modified diagonal element of R and + // the modified element of (Qty,0) + jacobian[k * cols + pk] = cos * rkk + sin * lmDiag[k]; + double temp = cos * work[k] + sin * qtbpj; + qtbpj = -sin * work[k] + cos * qtbpj; + work[k] = temp; + + // accumulate the tranformation in the row of s + for (int i = k + 1; i < solvedCols; ++i) { + double rik = jacobian[i * cols + pk]; + temp = cos * rik + sin * lmDiag[i]; + lmDiag[i] = -sin * rik + cos * lmDiag[i]; + jacobian[i * cols + pk] = temp; + } + + } + } + + // store the diagonal element of s and restore + // the corresponding diagonal element of R + int index = j * cols + permutation[j]; + lmDiag[j] = jacobian[index]; + jacobian[index] = lmDir[j]; + + } + + // solve the triangular system for z, if the system is + // singular, then obtain a least squares solution + int nSing = solvedCols; + for (int j = 0; j < solvedCols; ++j) { + if ((lmDiag[j] == 0) && (nSing == solvedCols)) { + nSing = j; + } + if (nSing < solvedCols) { + work[j] = 0; + } + } + if (nSing > 0) { + for (int j = nSing - 1; j >= 0; --j) { + int pj = permutation[j]; + double sum = 0; + for (int i = j + 1; i < nSing; ++i) { + sum += jacobian[i * cols + pj] * work[i]; + } + work[j] = (work[j] - sum) / lmDiag[j]; + } + } + + // permute the components of z back to components of lmDir + for (int j = 0; j < lmDir.length; ++j) { + lmDir[permutation[j]] = work[j]; + } + + } + + /** Decompose a matrix A as A.P = Q.R using Householder transforms. + *

    As suggested in the P. Lascaux and R. Theodor book + * Analyse numérique matricielle appliquée à + * l'art de l'ingénieur (Masson, 1986), instead of representing + * the Householder transforms with uk unit vectors such that: + *

    +   * Hk = I - 2uk.ukt
    +   * 
    + * we use k non-unit vectors such that: + *
    +   * Hk = I - betakvk.vkt
    +   * 
    + * where vk = ak - alphak ek. + * The betak coefficients are provided upon exit as recomputing + * them from the vk vectors would be costly.

    + *

    This decomposition handles rank deficient cases since the tranformations + * are performed in non-increasing columns norms order thanks to columns + * pivoting. The diagonal elements of the R matrix are therefore also in + * non-increasing absolute values order.

    + */ + private void qrDecomposition() { + + // initializations + for (int k = 0; k < cols; ++k) { + permutation[k] = k; + double norm2 = 0; + for (int index = k; index < jacobian.length; index += cols) { + double akk = jacobian[index]; + norm2 += akk * akk; + } + jacNorm[k] = Math.sqrt(norm2); + } + + // transform the matrix column after column + for (int k = 0; k < cols; ++k) { + + // select the column with the greatest norm on active components + int nextColumn = -1; + double ak2 = Double.NEGATIVE_INFINITY; + for (int i = k; i < cols; ++i) { + double norm2 = 0; + int iDiag = k * cols + permutation[i]; + for (int index = iDiag; index < jacobian.length; index += cols) { + double aki = jacobian[index]; + norm2 += aki * aki; + } + if (norm2 > ak2) { + nextColumn = i; + ak2 = norm2; + } + } + if (ak2 == 0) { + rank = k; + return; + } + int pk = permutation[nextColumn]; + permutation[nextColumn] = permutation[k]; + permutation[k] = pk; + + // choose alpha such that Hk.u = alpha ek + int kDiag = k * cols + pk; + double akk = jacobian[kDiag]; + double alpha = (akk > 0) ? -Math.sqrt(ak2) : Math.sqrt(ak2); + double betak = 1.0 / (ak2 - akk * alpha); + beta[pk] = betak; + + // transform the current column + diagR[pk] = alpha; + jacobian[kDiag] -= alpha; + + // transform the remaining columns + for (int dk = cols - 1 - k; dk > 0; --dk) { + int dkp = permutation[k + dk] - pk; + double gamma = 0; + for (int index = kDiag; index < jacobian.length; index += cols) { + gamma += jacobian[index] * jacobian[index + dkp]; + } + gamma *= betak; + for (int index = kDiag; index < jacobian.length; index += cols) { + jacobian[index + dkp] -= gamma * jacobian[index]; + } + } + + } + + rank = solvedCols; + + } + + /** Compute the product Qt.y for some Q.R. decomposition. + * @param y vector to multiply (will be overwritten with the result) + */ + private void qTy(double[] y) { + for (int k = 0; k < cols; ++k) { + int pk = permutation[k]; + int kDiag = k * cols + pk; + double gamma = 0; + for (int i = k, index = kDiag; i < rows; ++i, index += cols) { + gamma += jacobian[index] * y[i]; + } + gamma *= beta[pk]; + for (int i = k, index = kDiag; i < rows; ++i, index += cols) { + y[i] -= gamma * jacobian[index]; + } + } + } + + /** Array of measurements. */ + private WeightedMeasurement[] measurements; + + /** Array of parameters. */ + private EstimatedParameter[] parameters; + + /** Jacobian matrix. + *

    Depending on the computation phase, this matrix is either in + * canonical form (just after the calls to updateJacobian) or in + * Q.R. decomposed form (after calls to qrDecomposition)

    + */ + private double[] jacobian; + + /** Number of columns of the jacobian matrix. */ + private int cols; + + /** Number of solved variables. */ + private int solvedCols; + + /** Number of rows of the jacobian matrix. */ + private int rows; + + /** Diagonal elements of the R matrix in the Q.R. decomposition. */ + private double[] diagR; + + /** Norms of the columns of the jacobian matrix. */ + private double[] jacNorm; + + /** Coefficients of the Householder transforms vectors. */ + private double[] beta; + + /** Columns permutation array. */ + private int[] permutation; + + /** Rank of the jacobian matrix. */ + private int rank; + + /** Levenberg-Marquardt parameter. */ + private double lmPar; + + /** Parameters evolution direction associated with lmPar. */ + private double[] lmDir; + + /** Residuals array. + *

    Depending on the computation phase, this array is either in + * canonical form (just after the calls to updateResiduals) or in + * premultiplied by Qt form (just after calls to qTy)

    + */ + private double[] residuals; + + /** Cost value (square root of the sum of the residuals). */ + private double cost; + + /** Positive input variable used in determining the initial step bound. */ + private double initialStepBoundFactor; + + /** Maximal number of cost evaluations. */ + private int maxCostEval; + + /** Number of cost evaluations. */ + private int costEvaluations; + + /** Number of jacobian evaluations. */ + private int jacobianEvaluations; + + /** Desired relative error in the sum of squares. */ + private double costRelativeTolerance; + + /** Desired relative error in the approximate solution parameters. */ + private double parRelativeTolerance; + + /** Desired max cosine on the orthogonality between the function vector + * and the columns of the jacobian. */ + private double orthoTolerance; + + private static final long serialVersionUID = 5387476316105068340L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/estimation/WeightedMeasurement.java b/src/mantissa/src/org/spaceroots/mantissa/estimation/WeightedMeasurement.java new file mode 100644 index 000000000..09ce16663 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/estimation/WeightedMeasurement.java @@ -0,0 +1,143 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.estimation; + +import java.io.Serializable; + +/** This class represents measurements in estimation problems. + + *

    This abstract class implements all the methods needed to handle + * measurements in a general way. It defines neither the {@link + * #getTheoreticalValue getTheoreticalValue} nor the {@link + * #getPartial getPartial} methods, which should be defined by + * sub-classes according to the specific problem.

    + + *

    The {@link #getTheoreticalValue getTheoreticalValue} and {@link + * #getPartial getPartial} methods must always use the current + * estimate of the parameters set by the solver in the problem. These + * parameters can be retrieved through the {@link + * EstimationProblem#getAllParameters + * EstimationProblem.getAllParameters} method if the measurements are + * independant of the problem, or directly if they are implemented as + * inner classes of the problem.

    + + *

    The instances for which the ignored flag is set + * through the {@link #setIgnored setIgnored} method are ignored by the + * solvers. This can be used to reject wrong measurements at some + * steps of the estimation.

    + + * @see EstimationProblem + + * @version $Id: WeightedMeasurement.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public abstract class WeightedMeasurement implements Serializable { + + /** Simple constructor. + * Build a measurement with the given parameters, and set its ignore + * flag to false. + * @param weight weight of the measurement in the least squares problem + * (two common choices are either to use 1.0 for all measurements, or to + * use a value proportional to the inverse of the variance of the measurement + * type) + * @param measuredValue measured value + */ + public WeightedMeasurement(double weight, double measuredValue) { + this.weight = weight; + this.measuredValue = measuredValue; + ignored = false; + } + + /** Simple constructor. + * Build a measurement with the given parameters + * @param weight weight of the measurement in the least squares problem + * @param measuredValue measured value + * @param ignored true if the measurement should be ignored + */ + public WeightedMeasurement(double weight, double measuredValue, + boolean ignored) { + this.weight = weight; + this.measuredValue = measuredValue; + this.ignored = ignored; + } + + /** Get the weight of the measurement in the least squares problem + * @return weight + */ + public double getWeight() { + return weight; + } + + /** Get the measured value + * @return measured value + */ + public double getMeasuredValue() { + return measuredValue; + } + + /** Get the residual for this measurement + * The residual is the measured value minus the theoretical value. + * @return residual + */ + public double getResidual() { + return measuredValue - getTheoreticalValue(); + } + + /** Get the theoretical value expected for this measurement + *

    The theoretical value is the value expected for this measurement + * if the model and its parameter were all perfectly known.

    + *

    The value must be computed using the current estimate of the parameters + * set by the solver in the problem.

    + * @return theoretical value + */ + public abstract double getTheoreticalValue(); + + /** Get the partial derivative of the {@link #getTheoreticalValue + * theoretical value} according to the parameter. + *

    The value must be computed using the current estimate of the parameters + * set by the solver in the problem.

    + * @param parameter parameter against which the partial derivative + * should be computed + * @return partial derivative of the {@link #getTheoreticalValue + * theoretical value} + */ + public abstract double getPartial(EstimatedParameter parameter); + + /** Set the ignore flag to the specified value + * Setting the ignore flag to true allow to reject wrong + * measurements, which sometimes can be detected only rather late. + * @param ignored value for the ignore flag + */ + public void setIgnored(boolean ignored) { + this.ignored = ignored; + } + + /** Check if this measurement should be ignored + * @return true if the measurement should be ignored + */ + public boolean isIgnored() { + return ignored; + } + + private final double weight; + private final double measuredValue; + private boolean ignored; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/estimation/design/estimation.argo b/src/mantissa/src/org/spaceroots/mantissa/estimation/design/estimation.argo new file mode 100644 index 000000000..042778b32 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/estimation/design/estimation.argo @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/mantissa/src/org/spaceroots/mantissa/estimation/design/estimation.xmi b/src/mantissa/src/org/spaceroots/mantissa/estimation/design/estimation.xmi new file mode 100644 index 000000000..67baa87cc --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/estimation/design/estimation.xmi @@ -0,0 +1,868 @@ + + + + + Novosoft UML Library + 0.4.17 + + + + + + estimation + + + + + + + WeightedMeasurement + + + + + + + + + + + + + + + + + + weight_ + + + + + + + + + + + measuredValue_ + + + + + + + + + + + ignored_ + + + + + + + + + + + getWeight + + + + + + + + + + + + + + + + + + + + + + + + getMeasuredValue + + + + + + + + + + + + + + + + + + + + + + + + setIgnored + + + + + + + + + + + + + + + + + + + + + + ignored + + + + + + + + + + + + + isIgnored + + + + + + + + + + + + + + + + + + + + + + + + getTheoreticalValue + + + + + + + + + + + + + + + + + + + + + + + + getResidual + + + + + + + + + + + + + + + + + + + + + + + + getPartial + + + + + + + + + + + + + + + + + + + + + + parameter + + + + + + + + + + + + + + + double + + + + + + + + + + boolean + + + + + + + + + + EstimatedParameter[] + + + + + + + + + + WeightedMeasurement[] + + + + + + + + + + void + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + LeastSquaresEstimator + + + + + + + + + + + + + + maxIterations_ + + + + + + + + + + + convergence_ + + + + + + + + + + + + + + + + + + + + + + + + + realize + + + + + + + + + + + + + MyProblem + + + + + + + + + + + + + + + + + + + + + + + + + + realize + + + + + + + + + + + + + MyFirstMeasurementType + + + + + + + + + + + + + + + + + + + + + + + + + + MySecondMeasurementType + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 1 + + + + + + + + + + + + + + + int + + + + + + + + + + + EstimatedParameter + + + + + + + + + + + + + + name_ + + + + + + + + + + + estimate_ + + + + + + + + + + + bound_ + + + + + + + + + + + setEstimate + + + + + + + + + + + + + + + + + + + + + + estimate + + + + + + + + + + + + + getEstimate + + + + + + + + + + + + + + + + + + + + + + + + isBound + + + + + + + + + + + + + + + + + + + + + + + + + + String + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/mantissa/src/org/spaceroots/mantissa/estimation/design/estimation_classdiagram1.pgml b/src/mantissa/src/org/spaceroots/mantissa/estimation/design/estimation_classdiagram1.pgml new file mode 100644 index 000000000..92fce84b2 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/estimation/design/estimation_classdiagram1.pgml @@ -0,0 +1,815 @@ + + + + + + + + + + WeightedMeasurement + + protected double weight_ +protected double measuredValue_ +protected boolean ignored_ + public double getWeight() +public double getMeasuredValue() +public void setIgnored(boolean ignored) +public boolean isIgnored() +public double getTheoreticalValue() +public double getResidual() +public double getPartial(EstimatedParameter parameter) + + + + + + + + <<Interface>> + EstimationProblem + public WeightedMeasurement[] getMeasurements() +public EstimatedParameter[] getUnboundParameters() +public EstimatedParameter[] getAllParameters() + + + + + + + + <<Interface>> + Estimator + public void estimate(EstimationProblem problem) + + + + + + + + LeastSquaresEstimator + + private int maxIterations_ +private double convergence_ + + + + + + + + + MyProblem + + + + + + + + + + + MyFirstMeasurementType + + + + + + + + + + + MySecondMeasurementType + + + + + + + + + + + EstimatedParameter + + private String name_ +private double estimate_ +private boolean bound_ + public void setEstimate(double estimate) +public double getEstimate() +public boolean isBound() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + sourcePortFig="Fig0.0" + destPortFig="Fig5.0" + sourceFigNode="Fig0" + destFigNode="Fig5" + + + + + + + + + diff --git a/src/mantissa/src/org/spaceroots/mantissa/estimation/doc-files/org_spaceroots_mantissa_estimation_classes.png b/src/mantissa/src/org/spaceroots/mantissa/estimation/doc-files/org_spaceroots_mantissa_estimation_classes.png new file mode 100644 index 000000000..dafb88e09 Binary files /dev/null and b/src/mantissa/src/org/spaceroots/mantissa/estimation/doc-files/org_spaceroots_mantissa_estimation_classes.png differ diff --git a/src/mantissa/src/org/spaceroots/mantissa/estimation/package.html b/src/mantissa/src/org/spaceroots/mantissa/estimation/package.html new file mode 100644 index 000000000..c6eba68de --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/estimation/package.html @@ -0,0 +1,52 @@ + + +This package provides classes to solve estimation problems. + +

    The estimation problems considered here are parametric problems where a user model +depends on initially unknown scalar parameters and several measurements made on +values that depend on the model are available. As an example, one can consider the +flow rate of a river given rain data on its vicinity, or the center and radius of a +circle given points on a ring.

    + +

    One important class of estimation problems is weighted least squares problems. +They basically consist in finding the values for some parameters pk such +that a cost function J = sum(wi ri2) is minimized. +The various ri terms represent the deviation ri = +mesi - modi between the measurements and the parameterized +models. The wi factors are the measurements weights, they are often chosen +either all equal to 1.0 or proportional to the inverse of the variance of the +measurement type. The solver adjusts the values of the estimated parameters +pk which are not bound. It does not touch the parameters which have been +put in a bound state by the user.

    + +

    This package provides the {@link +org.spaceroots.mantissa.estimation.EstimatedParameter EstimatedParameter} class to +represent each estimated parameter, and the {@link +org.spaceroots.mantissa.estimation.WeightedMeasurement WeightedMeasurement} abstract +class the user can extend to define its measurements. All parameters and measurements +are then provided to some {@link org.spaceroots.mantissa.estimation.Estimator +Estimator} packed together in an {@link +org.spaceroots.mantissa.estimation.EstimationProblem EstimationProblem} instance +which acts only as a container. The package provides two common estimators for +weighted least squares problems, one based on the {@link +org.spaceroots.mantissa.estimation.GaussNewtonEstimator Gauss-Newton} method and the +other one based on the {@link +org.spaceroots.mantissa.estimation.LevenbergMarquardtEstimator Levenberg-Marquardt} +method.

    + +

    The class diagram for the public classes of this package is displayed below. The +orange boxes UserProblem, UserFirstMeasurementType and +UserSecondMeasurementType are exemple of what the user should create to +use this package: implement his own problem and measurement types using the {@link +org.spaceroots.mantissa.estimation.EstimationProblem} interface and {@link +org.spaceroots.mantissa.estimation.WeightedMeasurement} abstract class, and then use +one of the provided estimators (for example {@link +org.spaceroots.mantissa.estimation.GaussNewtonEstimator} or {@link +org.spaceroots.mantissa.estimation.LevenbergMarquardtEstimator}) to solve it. The +white boxes are the interfaces and classes already provided by the library.

    + + + +@author L. Maisonobe + + \ No newline at end of file diff --git a/src/mantissa/src/org/spaceroots/mantissa/fitting/AbstractCurveFitter.java b/src/mantissa/src/org/spaceroots/mantissa/fitting/AbstractCurveFitter.java new file mode 100644 index 000000000..d0250cf38 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/fitting/AbstractCurveFitter.java @@ -0,0 +1,286 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.fitting; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Iterator; + +import org.spaceroots.mantissa.estimation.*; + +/** This class is the base class for all curve fitting classes in the package. + + *

    This class handles all common features of curve fitting like the + * sample points handling. It declares two methods ({@link + * #valueAt} and {@link #partial}) which should be implemented by + * sub-classes to define the precise shape of the curve they + * represent.

    + + * @version $Id: AbstractCurveFitter.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public abstract class AbstractCurveFitter + implements EstimationProblem, Serializable { + + /** + * Simple constructor. + * @param n number of coefficients in the underlying function + * @param maxIterations maximum number of iterations allowed + * @param convergence criterion threshold below which we do not need + * to improve the criterion anymore + * @param steadyStateThreshold steady state detection threshold, the + * problem has reached a steady state (read converged) if + * Math.abs (Jn - Jn-1) < Jn * convergence, where + * Jn and Jn-1 are the current and + * preceding criterion value (square sum of the weighted residuals + * of considered measurements). + * @param epsilon threshold under which the matrix of the linearized + * problem is considered singular (see {@link + * org.spaceroots.mantissa.linalg.SquareMatrix#solve( + * org.spaceroots.mantissa.linalg.Matrix,double) SquareMatrix.solve}). + */ + protected AbstractCurveFitter(int n, + int maxIterations, + double convergence, + double steadyStateThreshold, + double epsilon) { + + coefficients = new EstimatedParameter[n]; + measurements = new ArrayList(); + measurementsArray = null; + this.maxIterations = maxIterations; + this.steadyStateThreshold = steadyStateThreshold; + this.convergence = convergence; + this.epsilon = epsilon; + + } + + /** + * Simple constructor. + * @param coefficients first estimate of the coefficients. A + * reference to this array is hold by the newly created object. Its + * elements will be adjusted during the fitting process and they will + * be set to the adjusted coefficients at the end. + * @param maxIterations maximum number of iterations allowed + * @param convergence criterion threshold below which we do not need + * to improve the criterion anymore + * @param steadyStateThreshold steady state detection threshold, the + * problem has reached a steady state (read converged) if + * Math.abs (Jn - Jn-1) < Jn * convergence, where + * Jn and Jn-1 are the current and + * preceding criterion value (square sum of the weighted residuals + * of considered measurements). + * @param epsilon threshold under which the matrix of the linearized + * problem is considered singular (see {@link + * org.spaceroots.mantissa.linalg.SquareMatrix#solve( + * org.spaceroots.mantissa.linalg.Matrix,double) SquareMatrix.solve}). + */ + protected AbstractCurveFitter(EstimatedParameter[] coefficients, + int maxIterations, + double convergence, + double steadyStateThreshold, + double epsilon) { + + this.coefficients = coefficients; + measurements = new ArrayList(); + measurementsArray = null; + this.maxIterations = maxIterations; + this.steadyStateThreshold = steadyStateThreshold; + this.convergence = convergence; + this.epsilon = epsilon; + } + + /** Add a weighted (x,y) pair to the sample. + * @param weight weight of this pair in the fit + * @param x abscissa + * @param y ordinate, we have y = f (x) + */ + public void addWeightedPair(double weight, double x, double y) { + measurementsArray = null; + measurements.add(new FitMeasurement(weight, x, y)); + } + + /** Perform the fitting. + + *

    This method compute the coefficients of the curve that best + * fit the sample of weighted pairs previously given through calls + * to the {@link #addWeightedPair addWeightedPair} method.

    + + * @return coefficients of the curve + * @exception EstimationException if the fitting is not possible + * (for example if the sample has to few independant points) + + */ + public double[] fit() + throws EstimationException { + // perform the fit using a linear least square estimator + new GaussNewtonEstimator(maxIterations, convergence, + steadyStateThreshold, epsilon).estimate(this); + + // extract the coefficients + double[] fittedCoefficients = new double[coefficients.length]; + for (int i = 0; i < coefficients.length; ++i) { + fittedCoefficients[i] = coefficients[i].getEstimate(); + } + + return fittedCoefficients; + + } + + public WeightedMeasurement[] getMeasurements() { + if (measurementsArray == null) { + measurementsArray = new FitMeasurement[measurements.size()]; + int i = 0; + for (Iterator iterator = measurements.iterator(); iterator.hasNext(); ++i) { + measurementsArray[i] = (FitMeasurement) iterator.next(); + } + } + return measurementsArray; + } + + /** Get the unbound parameters of the problem. + * For a curve fitting, none of the function coefficient is bound. + * @return unbound parameters + */ + public EstimatedParameter[] getUnboundParameters() { + return coefficients; + } + + /** Get all the parameters of the problem. + * @return parameters + */ + public EstimatedParameter[] getAllParameters() { + return coefficients; + } + + /** Utility method to sort the measurements with respect to the abscissa. + + *

    This method is provided as a utility for derived classes. As + * an example, the {@link HarmonicFitter} class needs it in order to + * compute a first guess of the coefficients to initialize the + * estimation algorithm.

    + + */ + protected void sortMeasurements() { + + // Since the samples are almost always already sorted, this + // method is implemented as an insertion sort that reorders the + // elements in place. Insertion sort is very efficient in this case. + FitMeasurement curr = (FitMeasurement) measurements.get(0); + for (int j = 1; j < measurements.size (); ++j) { + FitMeasurement prec = curr; + curr = (FitMeasurement) measurements.get(j); + if (curr.x < prec.x) { + // the current element should be inserted closer to the beginning + int i = j - 1; + FitMeasurement mI = (FitMeasurement) measurements.get(i); + while ((i >= 0) && (curr.x < mI.x)) { + measurements.set(i + 1, mI); + if (i-- != 0) { + mI = (FitMeasurement) measurements.get(i); + } else { + mI = null; + } + } + measurements.set(i + 1, curr); + curr = (FitMeasurement) measurements.get(j); + } + } + + // make sure subsequent calls to getMeasurements + // will not use the unsorted array + measurementsArray = null; + + } + + /** Get the value of the function at x according to the current parameters value. + * @param x abscissa at which the theoretical value is requested + * @return theoretical value at x + */ + public abstract double valueAt(double x); + + /** Get the derivative of the function at x with respect to parameter p. + * @param x abscissa at which the partial derivative is requested + * @param p parameter with respect to which the derivative is requested + * @return partial derivative + */ + public abstract double partial(double x, EstimatedParameter p); + + /** This class represents the fit measurements. + * One measurement is a weighted pair (x, y), where y = f + * (x) is the value of the function at x abscissa. This class + * is an inner class because the methods related to the computation + * of f values and derivative are proveded by the fitter + * implementations. + */ + public class FitMeasurement + extends WeightedMeasurement { + + /** Simple constructor. + * @param weight weight of the measurement in the fitting process + * @param x abscissa of the measurement + * @param y ordinate of the measurement + */ + public FitMeasurement(double weight, double x, double y) { + super(weight, y); + this.x = x; + } + + /** Get the value of the fitted function at x. + * @return theoretical value at the measurement abscissa + */ + public double getTheoreticalValue() { + return valueAt(x); + } + + /** Partial derivative with respect to one function coefficient. + * @param p parameter with respect to which the derivative is requested + * @return partial derivative + */ + public double getPartial(EstimatedParameter p) { + return partial(x, p); + } + + /** Abscissa of the measurement. */ + public final double x; + + private static final long serialVersionUID = -2682582852369995960L; + + } + + /** Coefficients of the function */ + protected EstimatedParameter[] coefficients; + + /** Measurements vector */ + protected List measurements; + + /** Measurements array. + * This array contains the same entries as measurements_, but in a + * different structure. + */ + private FitMeasurement[] measurementsArray; + + private int maxIterations; + private double convergence; + private double steadyStateThreshold; + private double epsilon; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/fitting/F2FP2Iterator.java b/src/mantissa/src/org/spaceroots/mantissa/fitting/F2FP2Iterator.java new file mode 100644 index 000000000..b4b387733 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/fitting/F2FP2Iterator.java @@ -0,0 +1,74 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.fitting; + +import java.io.Serializable; + +import org.spaceroots.mantissa.functions.FunctionException; +import org.spaceroots.mantissa.functions.ExhaustedSampleException; +import org.spaceroots.mantissa.functions.vectorial.SampledFunctionIterator; +import org.spaceroots.mantissa.functions.vectorial.VectorialValuedPair; + +/** This class provides sampled values of the function t -> [f(t)^2, f'(t)^2]. + + * This class is a helper class used to compute a first guess of the + * harmonic coefficients of a function f (t) = a cos (omega t + + * phi). + + * @see FFPIterator + * @see HarmonicCoefficientsGuesser + + * @version $Id: F2FP2Iterator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +class F2FP2Iterator + implements SampledFunctionIterator, Serializable { + + public F2FP2Iterator(AbstractCurveFitter.FitMeasurement[] measurements) { + ffpIterator = new FFPIterator(measurements); + } + + public int getDimension() { + return 2; + } + + public boolean hasNext() { + return ffpIterator.hasNext(); + } + + public VectorialValuedPair nextSamplePoint() + throws ExhaustedSampleException, FunctionException { + + // get the raw values from the underlying FFPIterator + VectorialValuedPair point = ffpIterator.nextSamplePoint(); + + // hack the values (to avoid building a new object) + double[] y = point.getY(); + y[0] *= y[0]; + y[1] *= y[1]; + return point; + + } + + private FFPIterator ffpIterator; + + private static final long serialVersionUID = -8113110433795298072L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/fitting/FFPIterator.java b/src/mantissa/src/org/spaceroots/mantissa/fitting/FFPIterator.java new file mode 100644 index 000000000..b483ad1eb --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/fitting/FFPIterator.java @@ -0,0 +1,100 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.fitting; + +import java.io.Serializable; + +import org.spaceroots.mantissa.functions.FunctionException; +import org.spaceroots.mantissa.functions.ExhaustedSampleException; +import org.spaceroots.mantissa.functions.vectorial.SampledFunctionIterator; +import org.spaceroots.mantissa.functions.vectorial.VectorialValuedPair; + +/** This class provides sampled values of the function t -> [f(t), f'(t)]. + + * This class is a helper class used to compute a first guess of the + * harmonic coefficients of a function f (t) = a cos (omega t + + * phi). + + * @see F2FP2Iterator + * @see HarmonicCoefficientsGuesser + + * @version $Id: FFPIterator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +class FFPIterator + implements SampledFunctionIterator, Serializable { + + public FFPIterator(AbstractCurveFitter.FitMeasurement[] measurements) { + this.measurements = measurements; + + // initialize the points of the raw sample + current = measurements[0]; + currentY = current.getMeasuredValue(); + next = measurements[1]; + nextY = next.getMeasuredValue(); + nextIndex = 2; + + } + + public int getDimension() { + return 2; + } + + public boolean hasNext() { + return nextIndex < measurements.length; + } + + public VectorialValuedPair nextSamplePoint() + throws ExhaustedSampleException, FunctionException { + if (nextIndex >= measurements.length) { + throw new ExhaustedSampleException(measurements.length); + } + + // shift the points + previous = current; + previousY = currentY; + current = next; + currentY = nextY; + next = measurements[nextIndex++]; + nextY = next.getMeasuredValue(); + + // return the two dimensions vector [f(x), f'(x)] + double[] table = new double[2]; + table[0] = currentY; + table[1] = (nextY - previousY) / (next.x - previous.x); + return new VectorialValuedPair(current.x, table); + + } + + private AbstractCurveFitter.FitMeasurement[] measurements; + private int nextIndex; + + private AbstractCurveFitter.FitMeasurement previous; + private double previousY; + + private AbstractCurveFitter.FitMeasurement current; + private double nextY; + + private AbstractCurveFitter.FitMeasurement next; + private double currentY; + + private static final long serialVersionUID = -3187229691615380125L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/fitting/HarmonicCoefficientsGuesser.java b/src/mantissa/src/org/spaceroots/mantissa/fitting/HarmonicCoefficientsGuesser.java new file mode 100644 index 000000000..e4c54b121 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/fitting/HarmonicCoefficientsGuesser.java @@ -0,0 +1,271 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.fitting; + +import java.io.Serializable; + +import org.spaceroots.mantissa.functions.FunctionException; +import org.spaceroots.mantissa.functions.ExhaustedSampleException; +import org.spaceroots.mantissa.functions.vectorial.SampledFunctionIterator; +import org.spaceroots.mantissa.functions.vectorial.VectorialValuedPair; + +import org.spaceroots.mantissa.quadrature.vectorial.EnhancedSimpsonIntegratorSampler; + +import org.spaceroots.mantissa.estimation.EstimationException; + +/** This class guesses harmonic coefficients from a sample. + + *

    The algorithm used to guess the coefficients is as follows:

    + + *

    We know f (t) at some sampling points ti and want to find a, + * omega and phi such that f (t) = a cos (omega t + phi). + *

    + * + *

    From the analytical expression, we can compute two primitives : + *

    + *     If2  (t) = int (f^2)  = a^2 * [t + S (t)] / 2
    + *     If'2 (t) = int (f'^2) = a^2 * omega^2 * [t - S (t)] / 2
    + *     where S (t) = sin (2 * (omega * t + phi)) / (2 * omega)
    + * 
    + *

    + * + *

    We can remove S between these expressions : + *

    + *     If'2 (t) = a^2 * omega ^ 2 * t - omega ^ 2 * If2 (t)
    + * 
    + *

    + * + *

    The preceding expression shows that If'2 (t) is a linear + * combination of both t and If2 (t): If'2 (t) = A * t + B * If2 (t) + *

    + * + *

    From the primitive, we can deduce the same form for definite + * integrals between t1 and ti for each ti : + *

    + *   If2 (ti) - If2 (t1) = A * (ti - t1) + B * (If2 (ti) - If2 (t1))
    + * 
    + *

    + * + *

    We can find the coefficients A and B that best fit the sample + * to this linear expression by computing the definite integrals for + * each sample points. + *

    + * + *

    For a bilinear expression z (xi, yi) = A * xi + B * yi, the + * coefficients a and b that minimize a least square criterion + * Sum ((zi - z (xi, yi))^2) are given by these expressions:

    + *
    + *
    + *         Sum (yi^2) Sum (xi zi) - Sum (xi yi) Sum (yi zi)
    + *     A = ------------------------------------------------
    + *         Sum (xi^2) Sum (yi^2)  - Sum (xi yi) Sum (xi yi)
    + *
    + *         Sum (xi^2) Sum (yi zi) - Sum (xi yi) Sum (xi zi)
    + *     B = ------------------------------------------------
    + *         Sum (xi^2) Sum (yi^2)  - Sum (xi yi) Sum (xi yi)
    + * 
    + *

    + * + * + *

    In fact, we can assume both a and omega are positive and + * compute them directly, knowing that A = a^2 * omega^2 and that + * B = - omega^2. The complete algorithm is therefore:

    + *
    + *
    + * for each ti from t1 to t(n-1), compute:
    + *   f  (ti)
    + *   f' (ti) = (f (t(i+1)) - f(t(i-1))) / (t(i+1) - t(i-1))
    + *   xi = ti - t1
    + *   yi = int (f^2) from t1 to ti
    + *   zi = int (f'^2) from t1 to ti
    + *   update the sums Sum (xi^2), Sum (yi^2),
    + *                   Sum (xi yi), Sum (xi zi)
    + *                   and Sum (yi zi)
    + * end for
    + *
    + *            |-------------------------------------------------
    + *         \  | Sum (yi^2) Sum (xi zi) - Sum (xi yi) Sum (yi zi)
    + * a     =  \ | ------------------------------------------------
    + *           \| Sum (xi yi) Sum (xi zi) - Sum (xi^2) Sum (yi zi)
    + *
    + *
    + *            |-------------------------------------------------
    + *         \  | Sum (xi yi) Sum (xi zi) - Sum (xi^2) Sum (yi zi)
    + * omega =  \ | ------------------------------------------------
    + *           \| Sum (xi^2) Sum (yi^2)  - Sum (xi yi) Sum (xi yi)
    + *
    + * 
    + *

    + + *

    Once we know omega, we can compute: + *

    + *    fc = omega * f (t) * cos (omega * t) - f' (t) * sin (omega * t)
    + *    fs = omega * f (t) * sin (omega * t) + f' (t) * cos (omega * t)
    + * 
    + *

    + + *

    It appears that fc = a * omega * cos (phi) and + * fs = -a * omega * sin (phi), so we can use these + * expressions to compute phi. The best estimate over the sample is + * given by averaging these expressions. + *

    + + *

    Since integrals and means are involved in the preceding + * estimations, these operations run in O(n) time, where n is the + * number of measurements.

    + + * @version $Id: HarmonicCoefficientsGuesser.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class HarmonicCoefficientsGuesser + implements Serializable{ + + public HarmonicCoefficientsGuesser(AbstractCurveFitter.FitMeasurement[] measurements) { + this.measurements = measurements; + a = Double.NaN; + omega = Double.NaN; + } + + /** Estimate a first guess of the coefficients. + + * @exception ExhaustedSampleException if the sample is exhausted. + + * @exception FunctionException if the integrator throws one. + + * @exception EstimationException if the sample is too short or if + * the first guess cannot be computed (when the elements under the + * square roots are negative). + * */ + public void guess() + throws ExhaustedSampleException, FunctionException, EstimationException { + guessAOmega(); + guessPhi(); + } + + /** Estimate a first guess of the a and omega coefficients. + + * @exception ExhaustedSampleException if the sample is exhausted. + + * @exception FunctionException if the integrator throws one. + + * @exception EstimationException if the sample is too short or if + * the first guess cannot be computed (when the elements under the + * square roots are negative). + + */ + private void guessAOmega() + throws ExhaustedSampleException, FunctionException, EstimationException { + + // initialize the sums for the linear model between the two integrals + double sx2 = 0.0; + double sy2 = 0.0; + double sxy = 0.0; + double sxz = 0.0; + double syz = 0.0; + + // build the integrals sampler + F2FP2Iterator iter = new F2FP2Iterator(measurements); + SampledFunctionIterator sampler = + new EnhancedSimpsonIntegratorSampler(iter); + VectorialValuedPair p0 = sampler.nextSamplePoint(); + double p0X = p0.getX(); + double[] p0Y = p0.getY(); + + // get the points for the linear model + while (sampler.hasNext()) { + + VectorialValuedPair point = sampler.nextSamplePoint(); + double pX = point.getX(); + double[] pY = point.getY(); + + double dx = pX - p0X; + double dy0 = pY[0] - p0Y[0]; + double dy1 = pY[1] - p0Y[1]; + + sx2 += dx * dx; + sy2 += dy0 * dy0; + sxy += dx * dy0; + sxz += dx * dy1; + syz += dy0 * dy1; + + } + + // compute the amplitude and pulsation coefficients + double c1 = sy2 * sxz - sxy * syz; + double c2 = sxy * sxz - sx2 * syz; + double c3 = sx2 * sy2 - sxy * sxy; + if ((c1 / c2 < 0.0) || (c2 / c3 < 0.0)) { + throw new EstimationException("unable to guess a first estimate"); + } + a = Math.sqrt(c1 / c2); + omega = Math.sqrt(c2 / c3); + + } + + /** Estimate a first guess of the phi coefficient. + + * @exception ExhaustedSampleException if the sample is exhausted. + + * @exception FunctionException if the sampler throws one. + + */ + private void guessPhi() + throws ExhaustedSampleException, FunctionException { + + SampledFunctionIterator iter = new FFPIterator(measurements); + + // initialize the means + double fcMean = 0.0; + double fsMean = 0.0; + + while (iter.hasNext()) { + VectorialValuedPair point = iter.nextSamplePoint(); + double omegaX = omega * point.getX(); + double[] pY = point.getY(); + double cosine = Math.cos(omegaX); + double sine = Math.sin(omegaX); + fcMean += omega * pY[0] * cosine - pY[1] * sine; + fsMean += omega * pY[0] * sine + pY[1] * cosine; + } + + phi = Math.atan2(-fsMean, fcMean); + + } + + public double getOmega() { + return omega; + } + + public double getA() { + return a; + } + + public double getPhi() { + return phi; + } + + private AbstractCurveFitter.FitMeasurement[] measurements; + private double a; + private double omega; + private double phi; + + private static final long serialVersionUID = 2400399048702758814L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/fitting/HarmonicFitter.java b/src/mantissa/src/org/spaceroots/mantissa/fitting/HarmonicFitter.java new file mode 100644 index 000000000..6693501e3 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/fitting/HarmonicFitter.java @@ -0,0 +1,197 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.fitting; + +import java.io.Serializable; + +import org.spaceroots.mantissa.estimation.*; +import org.spaceroots.mantissa.functions.ExhaustedSampleException; +import org.spaceroots.mantissa.functions.FunctionException; + +/** This class implements a curve fitting specialized for sinusoids. + + *

    Harmonic fitting is a very simple case of curve fitting. The + * estimated coefficients are the amplitude a, the pulsation omega and + * the phase phi: f (t) = a cos (omega t + phi). They are + * searched by a least square estimator initialized with a rough guess + * based on integrals.

    + + *

    This class is by no means optimized, neither versus + * space nor versus time performance.

    + + * @version $Id: HarmonicFitter.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class HarmonicFitter + extends AbstractCurveFitter + implements EstimationProblem, Serializable { + + /** + * Simple constructor. + * @param maxIterations maximum number of iterations allowed + * @param convergence criterion threshold below which we do not need + * to improve the criterion anymore + * @param steadyStateThreshold steady state detection threshold, the + * problem has reached a steady state (read converged) if + * Math.abs (Jn - Jn-1) < Jn * convergence, where + * Jn and Jn-1 are the current and + * preceding criterion value (square sum of the weighted residuals + * of considered measurements). + * @param epsilon threshold under which the matrix of the linearized + * problem is considered singular (see {@link + * org.spaceroots.mantissa.linalg.SquareMatrix#solve( + * org.spaceroots.mantissa.linalg.Matrix,double) SquareMatrix.solve}). + */ + public HarmonicFitter(int maxIterations, double convergence, + double steadyStateThreshold, double epsilon) { + super(3, maxIterations, convergence, steadyStateThreshold, epsilon); + coefficients[0] = new EstimatedParameter("a", 2.0 * Math.PI); + coefficients[1] = new EstimatedParameter("omega", 0.0); + coefficients[2] = new EstimatedParameter("phi", 0.0); + firstGuessNeeded = true; + } + + /** + * Simple constructor. + + *

    This constructor can be used when a first estimate of the + * coefficients is already known.

    + + * @param coefficients first estimate of the coefficients. + * A reference to this array is hold by the newly created + * object. Its elements will be adjusted during the fitting process + * and they will be set to the adjusted coefficients at the end. + * @param maxIterations maximum number of iterations allowed + * @param convergence criterion threshold below which we do not need + * to improve the criterion anymore + * @param steadyStateThreshold steady state detection threshold, the + * problem has reached a steady state (read converged) if + * Math.abs (Jn - Jn-1) < Jn * convergence, where + * Jn and Jn-1 are the current and + * preceding criterion value (square sum of the weighted residuals + * of considered measurements). + * @param epsilon threshold under which the matrix of the linearized + * problem is considered singular (see {@link + * org.spaceroots.mantissa.linalg.SquareMatrix#solve( + * org.spaceroots.mantissa.linalg.Matrix,double) SquareMatrix.solve}). + + */ + public HarmonicFitter(EstimatedParameter[] coefficients, + int maxIterations, double convergence, + double steadyStateThreshold, double epsilon) { + super(coefficients, + maxIterations, convergence, + steadyStateThreshold, epsilon); + firstGuessNeeded = false; + } + + public double[] fit() + throws EstimationException { + if (firstGuessNeeded) { + if (measurements.size() < 4) { + throw new EstimationException("sample must contain at least {0} points", + new String[] { + Integer.toString(4) + }); + } + + sortMeasurements(); + + try { + HarmonicCoefficientsGuesser guesser = + new HarmonicCoefficientsGuesser((FitMeasurement[]) getMeasurements()); + guesser.guess(); + + coefficients[0].setEstimate(guesser.getA()); + coefficients[1].setEstimate(guesser.getOmega()); + coefficients[2].setEstimate(guesser.getPhi()); + } catch(ExhaustedSampleException e) { + throw new EstimationException(e); + } catch(FunctionException e) { + throw new EstimationException(e); + } + + firstGuessNeeded = false; + + } + + return super.fit(); + + } + + /** Get the current amplitude coefficient estimate. + * Get a, where f (t) = a cos (omega t + phi) + * @return current amplitude coefficient estimate + */ + public double getAmplitude() { + return coefficients[0].getEstimate(); + } + + /** Get the current pulsation coefficient estimate. + * Get omega, where f (t) = a cos (omega t + phi) + * @return current pulsation coefficient estimate + */ + public double getPulsation() { + return coefficients[1].getEstimate(); + } + + /** Get the current phase coefficient estimate. + * Get phi, where f (t) = a cos (omega t + phi) + * @return current phase coefficient estimate + */ + public double getPhase() { + return coefficients[2].getEstimate(); + } + + /** Get the value of the function at x according to the current parameters value. + * @param x abscissa at which the theoretical value is requested + * @return theoretical value at x + */ + public double valueAt(double x) { + double a = coefficients[0].getEstimate(); + double omega = coefficients[1].getEstimate(); + double phi = coefficients[2].getEstimate(); + return a * Math.cos(omega * x + phi); + } + + /** Get the derivative of the function at x with respect to parameter p. + * @param x abscissa at which the partial derivative is requested + * @param p parameter with respect to which the derivative is requested + * @return partial derivative + */ + public double partial(double x, EstimatedParameter p) { + double a = coefficients[0].getEstimate(); + double omega = coefficients[1].getEstimate(); + double phi = coefficients[2].getEstimate(); + if (p == coefficients[0]) { + return Math.cos(omega * x + phi); + } else if (p == coefficients[1]) { + return -a * x * Math.sin(omega * x + phi); + } else { + return -a * Math.sin(omega * x + phi); + } + } + + /** Indicator of the need to compute a first guess of the coefficients. */ + private boolean firstGuessNeeded; + + private static final long serialVersionUID = -8722683066277473450L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/fitting/PolynomialCoefficient.java b/src/mantissa/src/org/spaceroots/mantissa/fitting/PolynomialCoefficient.java new file mode 100644 index 000000000..20ce5fd94 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/fitting/PolynomialCoefficient.java @@ -0,0 +1,44 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.fitting; + +import org.spaceroots.mantissa.estimation.EstimatedParameter; + +/** This class represents a polynomial coefficient. + + *

    Each coefficient is uniquely defined by its degree.

    + + * @see PolynomialFitter + + * @version $Id: PolynomialCoefficient.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ +public class PolynomialCoefficient + extends EstimatedParameter { + + public PolynomialCoefficient(int degree) { + super("a" + degree, 0.0); + this.degree = degree; + } + + public final int degree; + + private static final long serialVersionUID = 5775845068390259552L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/fitting/PolynomialFitter.java b/src/mantissa/src/org/spaceroots/mantissa/fitting/PolynomialFitter.java new file mode 100644 index 000000000..39bfb2961 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/fitting/PolynomialFitter.java @@ -0,0 +1,143 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.fitting; + +import java.io.Serializable; + +import org.spaceroots.mantissa.estimation.*; + +/** This class implements a curve fitting specialized for polynomials. + + *

    Polynomial fitting is a very simple case of curve fitting. The + * estimated coefficients are the polynom coefficients. They are + * searched by a least square estimator.

    + + *

    This class is by no means optimized, neither in + * space nor in time performance.

    + + * @see PolynomialCoefficient + + * @version $Id: PolynomialFitter.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class PolynomialFitter + extends AbstractCurveFitter + implements EstimationProblem, Serializable { + + /** + * Simple constructor. + + *

    The polynomial fitter built this way are complete polynoms, + * ie. a n-degree polynom has n+1 coefficients. In order to build + * fitter for sparse polynoms (for example a x^20 - b + * x^30, on should first build the coefficients array and + * provide it to {@link + * #PolynomialFitter(PolynomialCoefficient[], int, double, double, + * double)}.

    + * @param degree maximal degree of the polynom + * @param maxIterations maximum number of iterations allowed + * @param convergence criterion threshold below which we do not need + * to improve the criterion anymore + * @param steadyStateThreshold steady state detection threshold, the + * problem has reached a steady state (read converged) if + * Math.abs (Jn - Jn-1) < Jn * convergence, where + * Jn and Jn-1 are the current and + * preceding criterion value (square sum of the weighted residuals + * of considered measurements). + * @param epsilon threshold under which the matrix of the linearized + * problem is considered singular (see {@link + * org.spaceroots.mantissa.linalg.SquareMatrix#solve( + * org.spaceroots.mantissa.linalg.Matrix,double) SquareMatrix.solve}). + + */ + public PolynomialFitter(int degree, + int maxIterations, double convergence, + double steadyStateThreshold, double epsilon) { + + super(degree + 1, + maxIterations, steadyStateThreshold, + convergence, epsilon); + + for (int i = 0; i < coefficients.length; ++i) { + coefficients[i] = new PolynomialCoefficient(i); + } + + } + + /** + * Simple constructor. + + *

    This constructor can be used either when a first estimate of + * the coefficients is already known (which is of little interest + * because the fit cost is the same whether a first guess is known or + * not) or when one needs to handle sparse polynoms like a + * x^20 - b x^30.

    + + * @param coefficients first estimate of the coefficients. + * A reference to this array is hold by the newly created + * object. Its elements will be adjusted during the fitting process + * and they will be set to the adjusted coefficients at the end. + * @param maxIterations maximum number of iterations allowed + * @param convergence criterion threshold below which we do not need + * to improve the criterion anymore + * @param steadyStateThreshold steady state detection threshold, the + * problem has reached a steady state (read converged) if + * Math.abs (Jn - Jn-1) < Jn * convergence, where + * Jn and Jn-1 are the current and + * preceding criterion value (square sum of the weighted residuals + * of considered measurements). + * @param epsilon threshold under which the matrix of the linearized + * problem is considered singular (see {@link + * org.spaceroots.mantissa.linalg.SquareMatrix#solve( + * org.spaceroots.mantissa.linalg.Matrix,double) SquareMatrix.solve}). + + */ + public PolynomialFitter(PolynomialCoefficient[] coefficients, + int maxIterations, double convergence, + double steadyStateThreshold, double epsilon) { + super(coefficients, + maxIterations, steadyStateThreshold, + convergence, epsilon); + } + + /** Get the value of the function at x according to the current parameters value. + * @param x abscissa at which the theoretical value is requested + * @return theoretical value at x + */ + public double valueAt(double x) { + double y = coefficients[coefficients.length - 1].getEstimate(); + for (int i = coefficients.length - 2; i >= 0; --i) { + y = y * x + coefficients[i].getEstimate(); + } + return y; + } + + /** Get the derivative of the function at x with respect to parameter p. + * @param x abscissa at which the partial derivative is requested + * @param p parameter with respect to which the derivative is requested + * @return partial derivative + */ + public double partial(double x, EstimatedParameter p) { + return Math.pow(x, ((PolynomialCoefficient) p).degree); + } + + private static final long serialVersionUID = -226724596015163603L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/fitting/doc-files/org_spaceroots_mantissa_fitting_classes.png b/src/mantissa/src/org/spaceroots/mantissa/fitting/doc-files/org_spaceroots_mantissa_fitting_classes.png new file mode 100644 index 000000000..16e91a4cb Binary files /dev/null and b/src/mantissa/src/org/spaceroots/mantissa/fitting/doc-files/org_spaceroots_mantissa_fitting_classes.png differ diff --git a/src/mantissa/src/org/spaceroots/mantissa/fitting/package.html b/src/mantissa/src/org/spaceroots/mantissa/fitting/package.html new file mode 100644 index 000000000..46ddcc43d --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/fitting/package.html @@ -0,0 +1,26 @@ + + +This package provides classes to perform curve fitting. + +

    Curve fitting is a special case of an {@link +org.spaceroots.mantissa.estimation.EstimationProblem estimation problem} +were the parameters are the coefficients of a function f +whose graph y=f(x) should pass through sample points, and +were the measurements are the ordinates of the curve +yi=f(xi).

    + +

    The organisation of this package is explained in the class diagram +below. The {@link org.spaceroots.mantissa.fitting.AbstractCurveFitter +AbstractCurveFitter} class is the base class for all curve fitting +classes, it handles all common features like sample points +handling. Each specific curve fitting class should sub-class this +abstract class and implement the {@link +org.spaceroots.mantissa.fitting.AbstractCurveFitter#valueAt valueAt} and +{@link org.spaceroots.mantissa.fitting.AbstractCurveFitter#partial +partial} methods.

    + + + +@author L. Maisonobe + + diff --git a/src/mantissa/src/org/spaceroots/mantissa/functions/ExhaustedSampleException.java b/src/mantissa/src/org/spaceroots/mantissa/functions/ExhaustedSampleException.java new file mode 100644 index 000000000..bcc09eb15 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/functions/ExhaustedSampleException.java @@ -0,0 +1,42 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.functions; + +import org.spaceroots.mantissa.MantissaException; + +/** This class represents exceptions thrown by sample iterators. + + * @version $Id: ExhaustedSampleException.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class ExhaustedSampleException + extends MantissaException { + + /** Simple constructor. + * @param size size of the sample + */ + public ExhaustedSampleException(int size) { + super("sample contains only {0} elements", + new String[] { Integer.toString(size) }); + } + + private static final long serialVersionUID = -1490493298938282440L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/functions/FunctionException.java b/src/mantissa/src/org/spaceroots/mantissa/functions/FunctionException.java new file mode 100644 index 000000000..6422b97a2 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/functions/FunctionException.java @@ -0,0 +1,59 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.functions; + +import org.spaceroots.mantissa.MantissaException; + +/** This class represents exceptions thrown by scalar functions. + + * @version $Id: FunctionException.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class FunctionException + extends MantissaException { + + /** Simple constructor. + * Build an exception by translating and formating a message + * @param specifier format specifier (to be translated) + * @param parts to insert in the format (no translation) + */ + public FunctionException(String specifier, String[] parts) { + super(specifier, parts); + } + + /** Simple constructor. + * Build an exception by translating the specified message + * @param message message to translate + */ + public FunctionException(String message) { + super(message); + } + + /** Simple constructor. + * Build an exception from a cause + * @param cause cause of this exception + */ + public FunctionException(Throwable cause) { + super(cause); + } + + private static final long serialVersionUID = 1455885104381976115L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/functions/scalar/BasicSampledFunctionIterator.java b/src/mantissa/src/org/spaceroots/mantissa/functions/scalar/BasicSampledFunctionIterator.java new file mode 100644 index 000000000..7da7d2de8 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/functions/scalar/BasicSampledFunctionIterator.java @@ -0,0 +1,73 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.functions.scalar; + +import java.io.Serializable; + +import org.spaceroots.mantissa.functions.FunctionException; +import org.spaceroots.mantissa.functions.ExhaustedSampleException; + +/** This class is a simple wrapper allowing to iterate over a + * SampledFunction. + + *

    The basic implementation of the iteration interface does not + * perform any transformation on the sample, it only handles a loop + * over the underlying sampled function.

    + + * @see SampledFunction + + * @version $Id: BasicSampledFunctionIterator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ +public class BasicSampledFunctionIterator + implements SampledFunctionIterator, Serializable { + + /** Underlying sampled function. */ + private final SampledFunction function; + + /** Next sample element. */ + private int next; + + /** Simple constructor. + * Build an instance from a SampledFunction + * @param function smapled function over which we want to iterate + */ + public BasicSampledFunctionIterator(SampledFunction function) { + this.function = function; + next = 0; + } + + public boolean hasNext() { + return next < function.size(); + } + + public ScalarValuedPair nextSamplePoint() + throws ExhaustedSampleException, FunctionException { + if (next >= function.size()) { + throw new ExhaustedSampleException(function.size()); + } + + int current = next++; + return function.samplePointAt(current); + + } + + private static final long serialVersionUID = -9106690005598356403L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/functions/scalar/ComputableFunction.java b/src/mantissa/src/org/spaceroots/mantissa/functions/scalar/ComputableFunction.java new file mode 100644 index 000000000..1c74460c0 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/functions/scalar/ComputableFunction.java @@ -0,0 +1,55 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.functions.scalar; + +import org.spaceroots.mantissa.functions.FunctionException; + +/** This interface represents scalar functions of one real variable. + + *

    This interface should be implemented by all scalar functions + * that can be evaluated at any point. This does not imply that an + * explicit definition is available, a function given by an implicit + * function that should be numerically solved for each point for + * example is considered a computable function.

    + + *

    The {@link ComputableFunctionSampler} class can be used to + * transform classes implementing this interface into classes + * implementing the {@link SampledFunction} interface.

    + + *

    Several numerical algorithms (Gauss-Legendre integrators for + * example) need to choose themselves the evaluation points, so they + * can handle only objects that implement this interface.

    + + * @see org.spaceroots.mantissa.quadrature.scalar.ComputableFunctionIntegrator + * @see SampledFunction + + * @version $Id: ComputableFunction.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ +public interface ComputableFunction { + + /** Get the value of the function at the specified abscissa. + * @param x current abscissa + * @return function value + * @exception FunctionException if something goes wrong + */ + public double valueAt(double x) + throws FunctionException; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/functions/scalar/ComputableFunctionSampler.java b/src/mantissa/src/org/spaceroots/mantissa/functions/scalar/ComputableFunctionSampler.java new file mode 100644 index 000000000..0f06cb6de --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/functions/scalar/ComputableFunctionSampler.java @@ -0,0 +1,146 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.functions.scalar; + +import java.io.Serializable; + +import org.spaceroots.mantissa.functions.FunctionException; + +/** This class is a wrapper allowing to sample a + * {@link ComputableFunction}. + + *

    The sample produced is a regular sample. It can be specified by + * several means : + *

    + * In the latter case, the step can optionaly be adjusted in order to + * have the last point exactly at the upper bound of the range.

    + + *

    The sample points are computed on demand, they are not + * stored. This allow to use this method for very large sample with + * little memory overhead. The drawback is that if the same sample + * points are going to be requested several times, they will be + * recomputed each time. In this case, the user should consider + * storing the points by some other means.

    + + * @see ComputableFunction + + * @version $Id: ComputableFunctionSampler.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ +public class ComputableFunctionSampler + implements SampledFunction, Serializable { + + /** Underlying computable function. */ + private ComputableFunction function; + + /** Beginning abscissa. */ + private double begin; + + /** Step between points. */ + private double step; + + /** Total number of points. */ + private int n; + + /** + * Constructor. + + * Build a sample from an {@link ComputableFunction}. Beware of the + * classical off-by-one problem ! If you want to have a sample like + * this : 0.0, 0.1, 0.2 ..., 1.0, then you should specify step = 0.1 + * and n = 11 (not n = 10). + + * @param begin beginning of the range (will be the abscissa of the + * first point) + * @param step step between points + * @param n number of points + + */ + public ComputableFunctionSampler(ComputableFunction function, + double begin, double step, int n) { + this.function = function; + this.begin = begin; + this.step = step; + this.n = n; + } + + /** + * Constructor. + * Build a sample from an {@link ComputableFunction}. + + * @param range abscissa range (from range [0] to + * range [1]) + * @param n number of points + */ + public ComputableFunctionSampler(ComputableFunction function, + double[] range, int n) { + this.function = function; + begin = range[0]; + step = (range[1] - range[0]) / (n - 1); + this.n = n; + } + + /** + * Constructor. + * Build a sample from an {@link ComputableFunction}. + + * @param range abscissa range (from range [0] to + * range [1]) + * @param step step between points + * @param adjustStep if true, the step is reduced in order to have + * the last point of the sample exactly at range [1], + * if false the last point will be between range [1] - + * step and range [1] */ + public ComputableFunctionSampler(ComputableFunction function, + double[] range, double step, + boolean adjustStep) { + this.function = function; + begin = range [0]; + if (adjustStep) { + n = (int) Math.ceil((range[1] - range[0]) / step); + this.step = (range[1] - range[0]) / (n - 1); + } else { + n = (int) Math.floor((range[1] - range[0]) / step); + this.step = step; + } + } + + public int size() { + return n; + } + + public ScalarValuedPair samplePointAt(int index) + throws ArrayIndexOutOfBoundsException, FunctionException { + + if (index < 0 || index >= n) { + throw new ArrayIndexOutOfBoundsException(); + } + + double x = begin + index * step; + return new ScalarValuedPair(x, function.valueAt(x)); + + } + + private static final long serialVersionUID = -5127043442851795719L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/functions/scalar/SampledFunction.java b/src/mantissa/src/org/spaceroots/mantissa/functions/scalar/SampledFunction.java new file mode 100644 index 000000000..67d33149e --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/functions/scalar/SampledFunction.java @@ -0,0 +1,66 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.functions.scalar; + +import org.spaceroots.mantissa.functions.FunctionException; + +/** This interface represent sampled scalar functions. + + *

    A function sample is an ordered set of points of the form (x, y) + * where x is the abscissa of the point and y is the function value at + * x. It is typically a function that has been computed by external + * means or the result of measurements.

    + + *

    The {@link ComputableFunctionSampler} class can be used to + * transform classes implementing the {@link ComputableFunction} + * interface into classes implementing this interface.

    + + *

    Sampled functions cannot be directly handled by integrators + * implementing the {@link + * org.spaceroots.mantissa.quadrature.scalar.SampledFunctionIntegrator + * SampledFunctionIntegrator}. These integrators need a {@link + * SampledFunctionIterator} object to iterate over the + * sample.

    + + * @see SampledFunctionIterator + * @see ComputableFunctionSampler + * @see ComputableFunction + + * @version $Id: SampledFunction.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ +public interface SampledFunction { + + /** Get the number of points in the sample. + * @return number of points in the sample + */ + public int size(); + + /** Get the abscissa and value of the sample at the specified index. + * @param index index in the sample, should be between 0 and + * {@link #size} - 1 + * @return abscissa and value of the sample at the specified index + * @exception ArrayIndexOutOfBoundsException if the index is wrong + * @exception FunctionException if an eventual underlying function + * throws one + */ + public ScalarValuedPair samplePointAt(int index) + throws ArrayIndexOutOfBoundsException, FunctionException; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/functions/scalar/SampledFunctionIterator.java b/src/mantissa/src/org/spaceroots/mantissa/functions/scalar/SampledFunctionIterator.java new file mode 100644 index 000000000..d35a7b49b --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/functions/scalar/SampledFunctionIterator.java @@ -0,0 +1,47 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.functions.scalar; + +import org.spaceroots.mantissa.functions.FunctionException; +import org.spaceroots.mantissa.functions.ExhaustedSampleException; + +/** This interface provides iteration services over scalar functions + * samples. + + * @see SampledFunction + + * @version $Id: SampledFunctionIterator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ +public interface SampledFunctionIterator { + + /** Check if the iterator can provide another point. + * @return true if the iterator can provide another point. + */ + public boolean hasNext(); + + /** Get the next point of a sampled function. + * @return the next point of the sampled function + * @exception ExhaustedSampleException if the sample has been exhausted + * @exception FunctionException if the underlying function throws one + */ + public ScalarValuedPair nextSamplePoint() + throws ExhaustedSampleException, FunctionException; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/functions/scalar/ScalarValuedPair.java b/src/mantissa/src/org/spaceroots/mantissa/functions/scalar/ScalarValuedPair.java new file mode 100644 index 000000000..9a29d8c38 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/functions/scalar/ScalarValuedPair.java @@ -0,0 +1,97 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.functions.scalar; + +import java.io.Serializable; + +/** This class represents an (x, f(x)) pair for scalar functions. + + *

    A scalar function is a function of one scalar parameter x whose + * value is a scalar. This class is used has a simple placeholder to + * contain both an abscissa and the value of the function at this + * abscissa.

    + + * @see SampledFunction + * @see org.spaceroots.mantissa.functions.vectorial.VectorialValuedPair + + * @version $Id: ScalarValuedPair.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ +public class ScalarValuedPair + implements Serializable { + + /** Simple constructor. + * Build an instance from its coordinates + * @param x abscissa + * @param y ordinate (value of the function) + */ + public ScalarValuedPair(double x, double y) { + this.x = x; + this.y = y; + } + + /** Copy-constructor. + * @param p point to copy + */ + public ScalarValuedPair(ScalarValuedPair p) { + x = p.x; + y = p.y; + } + + /** + * Getter for the abscissa. + * @return value of the abscissa + */ + public double getX() { + return x; + } + + /** + * Getter for the ordinate. + * @return value of the ordinate + */ + public double getY() { + return y; + } + + /** + * Setter for the abscissa. + * @param x new value for the abscissa + */ + public void setX(double x) { + this.x = x; + } + + /** + * Setter for the ordinate. + * @param y new value for the ordinate + */ + public void setY(double y) { + this.y = y; + } + + /** Abscissa of the point. */ + private double x; + + /** Scalar ordinate of the point, y = f (x). */ + private double y; + + private static final long serialVersionUID = 1884346552569300794L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/functions/vectorial/BasicSampledFunctionIterator.java b/src/mantissa/src/org/spaceroots/mantissa/functions/vectorial/BasicSampledFunctionIterator.java new file mode 100644 index 000000000..781116a08 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/functions/vectorial/BasicSampledFunctionIterator.java @@ -0,0 +1,78 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.functions.vectorial; + +import java.io.Serializable; + +import org.spaceroots.mantissa.functions.FunctionException; +import org.spaceroots.mantissa.functions.ExhaustedSampleException; + +/** This class is a wrapper allowing to iterate over a + * SampledFunction. + + *

    The basic implementation of the iteration interface does not + * perform any transformation on the sample, it only handles a loop + * over the underlying sampled function.

    + + * @see SampledFunction + + * @version $Id: BasicSampledFunctionIterator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ +public class BasicSampledFunctionIterator + implements SampledFunctionIterator, Serializable { + + /** Simple constructor. + * Build an instance from a SampledFunction + * @param function smapled function over which we want to iterate + */ + public BasicSampledFunctionIterator(SampledFunction function) { + this.function = function; + next = 0; + } + + public int getDimension() { + return function.getDimension(); + } + + public boolean hasNext() { + return next < function.size(); + } + + public VectorialValuedPair nextSamplePoint() + throws ExhaustedSampleException, FunctionException { + + if (next >= function.size()) { + throw new ExhaustedSampleException(function.size()); + } + + int current = next++; + return function.samplePointAt(current); + + } + + /** Underlying sampled function. */ + private final SampledFunction function; + + /** Next sample element. */ + private int next; + + private static final long serialVersionUID = -4386278658288500627L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/functions/vectorial/ComputableFunction.java b/src/mantissa/src/org/spaceroots/mantissa/functions/vectorial/ComputableFunction.java new file mode 100644 index 000000000..3218bb083 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/functions/vectorial/ComputableFunction.java @@ -0,0 +1,59 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.functions.vectorial; + +import org.spaceroots.mantissa.functions.FunctionException; + +/** This interface represents vectorial functions of one real variable. + + *

    This interface should be implemented by all vectorial functions + * that can be evaluated at any point. This does not imply that an + * explicit definition is available, a function given by an implicit + * function that should be numerically solved for each point for + * example is considered a computable function.

    + + *

    The {@link ComputableFunctionSampler} class can be used to + * transform classes implementing this interface into classes + * implementing the {@link SampledFunction} interface.

    + + *

    Several numerical algorithms (Gauss-Legendre integrators for + * example) need to choose themselves the evaluation points, so they + * can handle only objects that implement this interface.

    + + * @see org.spaceroots.mantissa.quadrature.vectorial.ComputableFunctionIntegrator + * @see SampledFunction + + * @version $Id: ComputableFunction.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ +public interface ComputableFunction { + /** Get the dimension of the vectorial values of the function. + * @return dimension + */ + public int getDimension(); + + /** Get the value of the function at the specified abscissa. + * @param x current abscissa + * @return function value + * @exception FunctionException if something goes wrong + */ + public double[] valueAt(double x) + throws FunctionException; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/functions/vectorial/ComputableFunctionSampler.java b/src/mantissa/src/org/spaceroots/mantissa/functions/vectorial/ComputableFunctionSampler.java new file mode 100644 index 000000000..64670d7e5 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/functions/vectorial/ComputableFunctionSampler.java @@ -0,0 +1,150 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.functions.vectorial; + +import java.io.Serializable; + +import org.spaceroots.mantissa.functions.FunctionException; + +/** This class is a wrapper allowing to sample a + * {@link ComputableFunction}. + + *

    The sample produced is a regular sample. It can be specified by + * several means : + *

    + * In the latter case, the step can optionaly be adjusted in order to + * have the last point exactly at the upper bound of the range.

    + + *

    The sample points are computed on demand, they are not + * stored. This allow to use this method for very large sample with + * little memory overhead. The drawback is that if the same sample + * points are going to be requested several times, they will be + * recomputed each time. In this case, the user should consider + * storing the points by some other means.

    + + * @see ComputableFunction + + * @version $Id: ComputableFunctionSampler.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ +public class ComputableFunctionSampler + implements SampledFunction, Serializable { + + /** + * Constructor. + + * Build a sample from an {@link ComputableFunction}. Beware of the + * classical off-by-one problem ! If you want to have a sample like + * this : 0.0, 0.1, 0.2 ..., 1.0, then you should specify step = 0.1 + * and n = 11 (not n = 10). + + * @param begin beginning of the range (will be the abscissa of the + * first point) + * @param step step between points + * @param n number of points + + */ + public ComputableFunctionSampler(ComputableFunction function, + double begin, double step, int n) { + this.function = function; + this.begin = begin; + this.step = step; + this.n = n; + } + + /** + * Constructor. + * Build a sample from an {@link ComputableFunction}. + + * @param range abscissa range (from range [0] to + * range [1]) + * @param n number of points + */ + public ComputableFunctionSampler(ComputableFunction function, + double[] range, int n) { + this.function = function; + begin = range[0]; + step = (range[1] - range[0]) / (n - 1); + this.n = n; + } + + /** + * Constructor. + * Build a sample from an {@link ComputableFunction}. + + * @param range abscissa range (from range [0] to + * range [1]) + * @param step step between points + * @param adjustStep if true, the step is reduced in order to have + * the last point of the sample exactly at range [1], + * if false the last point will be between range [1] - + * step and range [1] */ + public ComputableFunctionSampler(ComputableFunction function, + double[] range, double step, + boolean adjustStep) { + this.function = function; + begin = range[0]; + if (adjustStep) { + n = (int) Math.ceil((range[1] - range[0]) / step); + this.step = (range[1] - range[0]) / (n - 1); + } else { + n = (int) Math.floor((range[1] - range[0]) / step); + this.step = step; + } + } + + public int size() { + return n; + } + + public int getDimension() { + return function.getDimension(); + } + + public VectorialValuedPair samplePointAt(int index) + throws ArrayIndexOutOfBoundsException, FunctionException { + + if (index < 0 || index >= n) { + throw new ArrayIndexOutOfBoundsException(); + } + + double x = begin + index * step; + return new VectorialValuedPair (x, function.valueAt(x)); + + } + + /** Underlying computable function. */ + private ComputableFunction function; + + /** Beginning abscissa. */ + private double begin; + + /** Step between points. */ + private double step; + + /** Total number of points. */ + private int n; + + private static final long serialVersionUID = 1368582688313212821L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/functions/vectorial/SampledFunction.java b/src/mantissa/src/org/spaceroots/mantissa/functions/vectorial/SampledFunction.java new file mode 100644 index 000000000..1f352aee3 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/functions/vectorial/SampledFunction.java @@ -0,0 +1,71 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.functions.vectorial; + +import org.spaceroots.mantissa.functions.FunctionException; + +/** This interface represent sampled vectorial functions. + + *

    A function sample is an ordered set of points of the form (x, y) + * where x is the abscissa of the point and y is the function value at + * x. It is typically a function that has been computed by external + * means or the result of measurements.

    + + *

    The {@link ComputableFunctionSampler} class can be used to + * transform classes implementing the {@link ComputableFunction} + * interface into classes implementing this interface.

    + + *

    Sampled functions cannot be directly handled by integrators + * implementing the {@link + * org.spaceroots.mantissa.quadrature.vectorial.SampledFunctionIntegrator + * SampledFunctionIntegrator}. These integrators need a {@link + * SampledFunctionIterator} object to iterate over the + * sample.

    + + * @see SampledFunctionIterator + * @see ComputableFunctionSampler + * @see ComputableFunction + + * @version $Id: SampledFunction.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ +public interface SampledFunction { + + /** Get the number of points in the sample. + * @return number of points in the sample + */ + public int size(); + + /** Get the dimension of the vectorial values of the function. + * @return dimension + */ + public int getDimension(); + + /** Get the abscissa and value of the sample at the specified index. + * @param index index in the sample, should be between 0 and + * {@link #size} - 1 + * @return abscissa and value of the sample at the specified index + * @exception ArrayIndexOutOfBoundsException if the index is wrong + * @exception FunctionException if an eventual underlying function + * throws one + */ + public VectorialValuedPair samplePointAt(int index) + throws ArrayIndexOutOfBoundsException, FunctionException; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/functions/vectorial/SampledFunctionIterator.java b/src/mantissa/src/org/spaceroots/mantissa/functions/vectorial/SampledFunctionIterator.java new file mode 100644 index 000000000..a580a7eed --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/functions/vectorial/SampledFunctionIterator.java @@ -0,0 +1,52 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.functions.vectorial; + +import org.spaceroots.mantissa.functions.FunctionException; +import org.spaceroots.mantissa.functions.ExhaustedSampleException; + +/** This interface provides iteration services over vectorial functions + * samples. + + * @see SampledFunction + + * @version $Id: SampledFunctionIterator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ +public interface SampledFunctionIterator { + + /** Get the dimension of the vectorial values of the function. + * @return dimension + */ + public int getDimension(); + + /** Check if the iterator can provide another point. + * @return true if the iterator can provide another point. + */ + public boolean hasNext(); + + /** Get the next point of a sampled function. + * @return the next point of the sampled function + * @exception ExhaustedSampleException if the sample has been exhausted + * @exception FunctionException if the underlying function throws one + */ + public VectorialValuedPair nextSamplePoint() + throws ExhaustedSampleException, FunctionException; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/functions/vectorial/VectorialValuedPair.java b/src/mantissa/src/org/spaceroots/mantissa/functions/vectorial/VectorialValuedPair.java new file mode 100644 index 000000000..a42e7a7ad --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/functions/vectorial/VectorialValuedPair.java @@ -0,0 +1,99 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.functions.vectorial; + +import java.io.Serializable; + +/** This class represents an (x, f(x)) pair for vectorial functions. + + *

    A vectorial function is a function of one vectorial parameter x whose + * value is a vector. This class is used has a simple placeholder to + * contain both an abscissa and the value of the function at this + * abscissa.

    + + * @see SampledFunction + * @see org.spaceroots.mantissa.functions.vectorial.VectorialValuedPair + + * @version $Id: VectorialValuedPair.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ +public class VectorialValuedPair + implements Serializable { + + /** + * Simple constructor. + * Build an instance from its coordinates + * @param x abscissa + * @param y ordinate (value of the function) + */ + public VectorialValuedPair(double x, double[] y) { + this.x = x; + this.y = y; + } + + /** + * Copy-constructor. + * @param p point to copy + */ + public VectorialValuedPair(VectorialValuedPair p) { + x = p.x; + y = p.y; + } + + /** + * Getter for the abscissa. + * @return value of the abscissa + */ + public double getX() { + return x; + } + + /** + * Getter for the ordinate. + * @return value of the ordinate + */ + public double[] getY() { + return y; + } + + /** + * Setter for the abscissa. + * @param x new value for the abscissa + */ + public void setX(double x) { + this.x = x; + } + + /** + * Setter for the ordinate. + * @param y new value for the ordinate + */ + public void setY(double[] y) { + this.y = y; + } + + /** Abscissa of the point. */ + private double x; + + /** Vectorial ordinate of the point, y = f (x). */ + private double[] y; + + private static final long serialVersionUID = -1336411215846160578L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/geometry/CardanEulerSingularityException.java b/src/mantissa/src/org/spaceroots/mantissa/geometry/CardanEulerSingularityException.java new file mode 100644 index 000000000..d1b910adf --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/geometry/CardanEulerSingularityException.java @@ -0,0 +1,44 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.geometry; + +import org.spaceroots.mantissa.MantissaException; + +/** This class represents exceptions thrown while extractiong Cardan + * or Euler angles from a rotation. + + * @version $Id: CardanEulerSingularityException.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class CardanEulerSingularityException + extends MantissaException { + + /** Simple constructor. + * build an exception with a default message. + * @param isCardan if true, the rotation is related to Cardan angles, + * if false it is related to EulerAngles + */ + public CardanEulerSingularityException(boolean isCardan) { + super(isCardan ? "Cardan angles singularity" : "Euler angles singularity"); + } + + private static final long serialVersionUID = -1360952845582206770L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/geometry/ImmutableVector3D.java b/src/mantissa/src/org/spaceroots/mantissa/geometry/ImmutableVector3D.java new file mode 100644 index 000000000..54194f268 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/geometry/ImmutableVector3D.java @@ -0,0 +1,204 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.geometry; + +/** + * This class implements immutable vectors in a three-dimensional space. + + * @version $Id: ImmutableVector3D.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class ImmutableVector3D + extends Vector3D { + + /** Simple constructor. + * Build a vector from its coordinates + * @param x abscissa + * @param y ordinate + * @param z height + */ + public ImmutableVector3D(double x, double y, double z) { + super(x, y, z); + computeNorm(); + } + + /** Simple constructor. + * Build a vector from its azimuthal coordinates + * @param alpha azimuth around Z + * (0 is +X, PI/2 is +Y, PI is -X and 3PI/2 is -Y) + * @param delta elevation above (XY) plane, from -PI to +PI + */ + public ImmutableVector3D(double alpha, double delta) { + super(alpha, delta); + computeNorm(); + } + + /** Copy constructor. + * Build a copy of a vector + * @param v vector to copy + */ + public ImmutableVector3D(Vector3D v) { + super(v); + computeNorm(); + } + + /** Multiplicative constructor + * Build a vector from another one and a scale factor. + * The vector built will be a * u + * @param a scale factor + * @param u base (unscaled) vector + */ + public ImmutableVector3D(double a, Vector3D u) { + super(a, u); + computeNorm(); + } + + /** Linear constructor + * Build a vector from two other ones and corresponding scale factors. + * The vector built will be a * u + b * v + * @param a first scale factor + * @param u first base (unscaled) vector + * @param b second scale factor + * @param v second base (unscaled) vector + */ + public ImmutableVector3D(double a, Vector3D u, double b, Vector3D v) { + super(a, u, b, v); + computeNorm(); + } + + /** Set the abscissa of the vector. + * This method should not be called for immutable vectors, it always + * throws an UnsupportedOperationException exception + * @param x new abscissa for the vector + * @exception UnsupportedOperationException thrown in every case + */ + public void setX(double x) { + throw new UnsupportedOperationException("vector is immutable"); + } + + /** Set the ordinate of the vector. + * This method should not be called for immutable vectors, it always + * throws an UnsupportedOperationException exception + * @param y new ordinate for the vector + * @exception UnsupportedOperationException thrown in every case + */ + public void setY(double y) { + throw new UnsupportedOperationException("vector is immutable"); + } + + /** Set the height of the vector. + * This method should not be called for immutable vectors, it always + * throws an UnsupportedOperationException exception + * @param z new height for the vector + * @exception UnsupportedOperationException thrown in every case + */ + public void setZ(double z) { + throw new UnsupportedOperationException("vector is immutable"); + } + + /** Set all coordinates of the vector. + * This method should not be called for immutable vectors, it always + * throws an UnsupportedOperationException exception + * @param x new abscissa for the vector + * @param y new ordinate for the vector + * @param z new height for the vector + * @exception UnsupportedOperationException thrown in every case + */ + public void setCoordinates(double x, double y, double z) { + throw new UnsupportedOperationException("vector is immutable"); + } + + /** Compute the norm once and for all. */ + private void computeNorm() { + norm = Math.sqrt(x * x + y * y + z * z); + } + + /** Get the norm for the vector. + * @return euclidian norm for the vector + */ + public double getNorm() { + return norm; + } + + /** Add a vector to the instance. + * This method should not be called for immutable vectors, it always + * throws an UnsupportedOperationException exception + * @param v vector to add + * @exception UnsupportedOperationException thrown in every case + */ + public void addToSelf(Vector3D v) { + throw new UnsupportedOperationException("vector is immutable"); + } + + /** Subtract a vector from the instance. + * This method should not be called for immutable vectors, it always + * throws an UnsupportedOperationException exception + * @param v vector to subtract + * @exception UnsupportedOperationException thrown in every case + */ + public void subtractFromSelf(Vector3D v) { + throw new UnsupportedOperationException("vector is immutable"); + } + + /** Normalize the instance. + * This method should not be called for immutable vectors, it always + * throws an UnsupportedOperationException exception + * @exception UnsupportedOperationException thrown in every case + */ + public void normalizeSelf() { + throw new UnsupportedOperationException("vector is immutable"); + } + + /** Revert the instance. + * This method should not be called for immutable vectors, it always + * throws an UnsupportedOperationException exception + * @exception UnsupportedOperationException thrown in every case + */ + public void negateSelf() { + throw new UnsupportedOperationException("vector is immutable"); + } + + /** Multiply the instance by a scalar + * This method should not be called for immutable vectors, it always + * throws an UnsupportedOperationException exception + * @param a scalar by which the instance should be multiplied + * @exception UnsupportedOperationException thrown in every case + */ + public void multiplySelf(double a) { + throw new UnsupportedOperationException("vector is immutable"); + } + + /** Reinitialize internal state from the specified array slice data. + * This method should not be called for immutable vectors, it always + * throws an UnsupportedOperationException exception + * @param start start index in the array + * @param array array holding the data to extract + * @exception UnsupportedOperationException thrown in every case + */ + public void mapStateFromArray(int start, double[] array) { + throw new UnsupportedOperationException("vector is immutable"); + } + + /** Norm of the vector. */ + private double norm; + + private static final long serialVersionUID = 5377895850033895270L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/geometry/NotARotationMatrixException.java b/src/mantissa/src/org/spaceroots/mantissa/geometry/NotARotationMatrixException.java new file mode 100644 index 000000000..71018f2cb --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/geometry/NotARotationMatrixException.java @@ -0,0 +1,44 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.geometry; + +import org.spaceroots.mantissa.MantissaException; + +/** This class represents exceptions thrown while building rotations + * from matrices. + + * @version $Id: NotARotationMatrixException.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class NotARotationMatrixException + extends MantissaException { + + /** Simple constructor. + * Build an exception by translating and formating a message + * @param specifier format specifier (to be translated) + * @param parts to insert in the format (no translation) + */ + public NotARotationMatrixException(String specifier, String[] parts) { + super(specifier, parts); + } + + private static final long serialVersionUID = 5647178478658937642L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/geometry/Rotation.java b/src/mantissa/src/org/spaceroots/mantissa/geometry/Rotation.java new file mode 100644 index 000000000..5e84a038b --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/geometry/Rotation.java @@ -0,0 +1,1141 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.geometry; + +import org.spaceroots.mantissa.utilities.ArraySliceMappable; +import java.io.Serializable; + +/** + * This class implements rotations in a three-dimensional space. + + *

    Rotations can be represented by several different mathematical + * entities (matrices, axe and angle, Cardan or Euler angles, + * quaternions). This class is an higher level abstraction, more + * user-oriented and hiding this implementation detail. Well, for the + * curious, we use quaternions for the internal representation. The + * user can build a rotation from any of these representations, and + * any of these representations can be retrieved from a + * Rotation instance (see the various constructors and + * getters). In addition, a rotation can also be built implicitely + * from a set of vectors before and after it has been applied. This + * means that this class can be used to compute transformations from + * one representation to another one. For example, extracting a set of + * Cardan angles from a rotation matrix can be done using one single + * line of code:

    + *
    + * double[] angles = new Rotation(matrix, 1.0e-10).getAngles(RotationOrder.XYZ);
    + * 
    + *

    Focus is more oriented on what a rotation do. Once it + * has been built, and regardless of its representation, a rotation is + * an operator which basically transforms three dimensional + * {@link Vector3D vectors} into other three dimensional {@link + * Vector3D vectors}. Depending on the application, the meaning of + * these vectors can vary. For example in an attitude simulation tool, + * you will often consider the vector is fixed and you transform its + * coordinates in one frame into its coordinates in another frame. In + * this case, the rotation implicitely defines the relation between + * the two frames. Another example could be a telescope control application, + * where the rotation would transform the sighting direction at rest + * into the desired observing direction. In this case the frame is the + * same (probably a topocentric one) and the raw and transformed + * vectors are different. In many case, both approaches will be + * combined, in our telescope example, we will probably also need to + * transform the observing direction in the topocentric frame into the + * observing direction in inertial frame taking into account the + * observatory location and the earth rotation.

    + + *

    These examples show that a rotation is what the user wants it to + * be, so this class does not push the user towards one specific + * definition and hence does not provide methods like + * projectVectorIntoDestinationFrame or + * computeTransformedDirection. It provides simpler and + * more generic methods: {@link #applyTo(Vector3D) applyTo(Vector3D)} + * and {@link #applyInverseTo(Vector3D) applyInverseTo(Vector3D)}.

    + + *

    Since a rotation is basically a vectorial operator, several + * rotations can be composed together and the composite operation + * r = r1 o r2 (which means that for each vector + * u, r(u) = r1(r2(u))) is also a + * rotation. Hence we can consider that in addition to vectors, a + * rotation can be applied to other rotations (or to itself). With our + * previous notations, we would say we can apply r1 to + * r2 and the result we get is r = r1 o + * r2. For this purpose, the class provides the methods: {@link + * #applyTo(Rotation) applyTo(Rotation)} and {@link + * #applyInverseTo(Rotation) applyInverseTo(Rotation)}.

    + + * @version $Id: Rotation.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + * @see Vector3D + * @see RotationOrder + + */ + +public class Rotation + implements ArraySliceMappable, Serializable { + + /** Build the identity rotation. + */ + public Rotation() { + q0 = 1; + q1 = 0; + q2 = 0; + q3 = 0; + } + + /** Build a rotation from the quaternion coordinates. + * @param q0 scalar part of the quaternion + * @param q1 first coordinate of the vectorial part of the quaternion + * @param q2 second coordinate of the vectorial part of the quaternion + * @param q3 third coordinate of the vectorial part of the quaternion + * @deprecated since Mantissa 6.3, this method as been deprecated as it + * does not properly handles non-normalized quaternions, it should be + * replaced by {@link #Rotation(double, double, double, double, boolean)} + */ + public Rotation(double q0, double q1, double q2, double q3) { + this.q0 = q0; + this.q1 = q1; + this.q2 = q2; + this.q3 = q3; + } + + /** Build a rotation from the quaternion coordinates. + *

    A rotation can be built from a normalized quaternion, + * i.e. a quaternion for which q02 + + * q12 + q22 + + * q32 = 1. If the quaternion is not normalized, + * the constructor can normalize it in a preprocessing step.

    + *

    This method replaces the {@link #Rotation(double, double, + * double, double) constructor using only 4 doubles} which was deprecated + * as of version 6.3 of Mantissa.

    + * @param q0 scalar part of the quaternion + * @param q1 first coordinate of the vectorial part of the quaternion + * @param q2 second coordinate of the vectorial part of the quaternion + * @param q3 third coordinate of the vectorial part of the quaternion + * @param needsNormalization if true, the coordinates are considered + * not to be normalized, a normalization preprocessing step is performed + * before using them + */ + public Rotation(double q0, double q1, double q2, double q3, + boolean needsNormalization) { + + if (needsNormalization) { + // normalization preprocessing + double inv = 1.0 / Math.sqrt(q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3); + q0 *= inv; + q1 *= inv; + q2 *= inv; + q3 *= inv; + } + + this.q0 = q0; + this.q1 = q1; + this.q2 = q2; + this.q3 = q3; + + } + + /** Build a rotation from an axis and an angle. + *

    We use the convention that angles are oriented according to + * the effect of the rotation on vectors around the axis. That means + * that if (i, j, k) is a direct frame and if we first provide +k as + * the axis and PI/2 as the angle to this constructor, and then + * {@link #applyTo(Vector3D) apply} the instance to +i, we will get + * +j.

    + * @param axis axis around which to rotate + * @param angle rotation angle. + * @exception ArithmeticException if the axis norm is null + */ + public Rotation(Vector3D axis, double angle) { + + double norm = axis.getNorm(); + if (norm == 0) { + throw new ArithmeticException("null norm"); + } + + double halfAngle = -0.5 * angle; + double coeff = Math.sin(halfAngle) / norm; + + q0 = Math.cos (halfAngle); + q1 = coeff * axis.getX(); + q2 = coeff * axis.getY(); + q3 = coeff * axis.getZ(); + + } + + /** Build a rotation from a 3X3 matrix. + + *

    Rotation matrices are orthogonal matrices, i.e. unit matrices + * (which are matrices for which m.mT = I) with real + * coefficients. The module of the determinant of unit matrices is + * 1, among the orthogonal 3X3 matrices, only the ones having a + * positive determinant (+1) are rotation matrices.

    + + *

    When a rotation is defined by a matrix with truncated values + * (typically when it is extracted from a technical sheet where only + * four to five significant digits are available), the matrix is not + * orthogonal anymore. This constructor handles this case + * transparently by using a copy of the given matrix and applying a + * correction to the copy in order to perfect its orthogonality. If + * the Frobenius norm of the correction needed is above the given + * threshold, then the matrix is considered to be too far from a + * true rotation matrix and an exception is thrown.

    + + * @param m rotation matrix + * @param threshold convergence threshold for the iterative + * orthogonality correction (convergence is reached when the + * difference between two steps of the Frobenius norm of the + * correction is below this threshold) + + * @exception NotARotationMatrixException if the matrix is not a 3X3 + * matrix, or if it cannot be transformed into an orthogonal matrix + * with the given threshold, or if the determinant of the resulting + * orthogonal matrix is negative + + */ + public Rotation(double[][] m, double threshold) + throws NotARotationMatrixException { + + // dimension check + if ((m.length != 3) || (m[0].length != 3) + || (m[1].length != 3) || (m[2].length != 3)) { + throw new NotARotationMatrixException("a {0}x{1} matrix" + + " cannot be a rotation matrix", + new String[] { + Integer.toString(m.length), + Integer.toString(m[0].length) + }); + } + + // compute a "close" orthogonal matrix + double[][] ort = orthogonalizeMatrix(m, threshold); + + // check the sign of the determinant + double det = ort[0][0] * (ort[1][1] * ort[2][2] - ort[2][1] * ort[1][2]) + - ort[1][0] * (ort[0][1] * ort[2][2] - ort[2][1] * ort[0][2]) + + ort[2][0] * (ort[0][1] * ort[1][2] - ort[1][1] * ort[0][2]); + if (det < 0.0) { + throw new NotARotationMatrixException("the closest orthogonal matrix" + + " has a negative determinant {0}", + new String[] { + Double.toString(det) + }); + } + + // There are different ways to compute the quaternions elements + // from the matrix. They all involve computing one element from + // the diagonal of the matrix, and computing the three other ones + // unsing a formula involving a division by the first element, + // which unfortunately can be null. Since the norm of the + // quaternion is 1, we know at least one element has an absolute + // value greater or equal to 0.5, so it is always possible to + // select the right formula and avoid division by zero and even + // numerical inaccuracy. Checking the elements in turn and using + // the first one greater than 0.45 is safe (this leads to a simple + // test since qi = 0.45 implies 4 qi^2 - 1 = -0.19) + double s = ort[0][0] + ort[1][1] + ort[2][2]; + if (s > -0.19) { + // compute q0 and deduce q1, q2 and q3 + q0 = 0.5 * Math.sqrt(s + 1.0); + double inv = 0.25 / q0; + q1 = inv * (ort[1][2] - ort[2][1]); + q2 = inv * (ort[2][0] - ort[0][2]); + q3 = inv * (ort[0][1] - ort[1][0]); + } else { + s = ort[0][0] - ort[1][1] - ort[2][2]; + if (s > -0.19) { + // compute q1 and deduce q0, q2 and q3 + q1 = 0.5 * Math.sqrt(s + 1.0); + double inv = 0.25 / q1; + q0 = inv * (ort[1][2] - ort[2][1]); + q2 = inv * (ort[0][1] + ort[1][0]); + q3 = inv * (ort[0][2] + ort[2][0]); + } else { + s = ort[1][1] - ort[0][0] - ort[2][2]; + if (s > -0.19) { + // compute q2 and deduce q0, q1 and q3 + q2 = 0.5 * Math.sqrt(s + 1.0); + double inv = 0.25 / q2; + q0 = inv * (ort[2][0] - ort[0][2]); + q1 = inv * (ort[0][1] + ort[1][0]); + q3 = inv * (ort[2][1] + ort[1][2]); + } else { + // compute q3 and deduce q0, q1 and q2 + s = ort[2][2] - ort[0][0] - ort[1][1]; + q3 = 0.5 * Math.sqrt(s + 1.0); + double inv = 0.25 / q3; + q0 = inv * (ort[0][1] - ort[1][0]); + q1 = inv * (ort[0][2] + ort[2][0]); + q2 = inv * (ort[2][1] + ort[1][2]); + } + } + } + + } + + /** Build the rotation that transforms a pair of vector into another pair. + + *

    Except for possible scale factors, if the instance were + * applied to the pair (u1, u2) it will produce the pair (v1, + * v2).

    + + *

    If the angular separation between u1 and u2 is not the same as + * the angular separation between v1 and v2, then a corrected v2' + * will be used rather than v2, the corrected vector will be in the + * (v1, v2) plane.

    + + * @param u1 first vector of the origin pair + * @param u2 second vector of the origin pair + * @param v1 desired image of u1 by the rotation + * @param v2 desired image of u2 by the rotation + */ + public Rotation(Vector3D u1, Vector3D u2, Vector3D v1, Vector3D v2) { + + // norms computation + double u1u1 = Vector3D.dotProduct(u1, u1); + double u2u2 = Vector3D.dotProduct(u2, u2); + double v1v1 = Vector3D.dotProduct(v1, v1); + double v2v2 = Vector3D.dotProduct(v2, v2); + if ((u1u1 < 1.0e-15) || (u2u2 < 1.0e-15) + || (v1v1 < 1.0e-15) || (v2v2 < 1.0e-15)) + throw new ArithmeticException("null norm"); + + double u1x = u1.getX(); + double u1y = u1.getY(); + double u1z = u1.getZ(); + + double u2x = u2.getX(); + double u2y = u2.getY(); + double u2z = u2.getZ(); + + // renormalize v1 in order to have (v1'|v1') = (u1|u1) + double coeff = Math.sqrt (u1u1 / v1v1); + double v1x = coeff * v1.getX(); + double v1y = coeff * v1.getY(); + double v1z = coeff * v1.getZ(); + v1 = new Vector3D(v1x, v1y, v1z); + + // adjust v2 in order to have (u1|u2) = (v1|v2) and (v2'|v2') = (u2|u2) + double u1u2 = Vector3D.dotProduct(u1, u2); + double v1v2 = Vector3D.dotProduct(v1, v2); + double coeffU = u1u2 / u1u1; + double coeffV = v1v2 / u1u1; + double beta = Math.sqrt((u2u2 - u1u2 * coeffU) / (v2v2 - v1v2 * coeffV)); + double alpha = coeffU - beta * coeffV; + double v2x = alpha * v1x + beta * v2.getX(); + double v2y = alpha * v1y + beta * v2.getY(); + double v2z = alpha * v1z + beta * v2.getZ(); + v2 = new Vector3D(v2x, v2y, v2z); + + // preliminary computation (we use explicit formulation instead + // of relying on the Vector3D class in order to avoid building lots + // of temporary objects) + Vector3D uRef = u1; + Vector3D vRef = v1; + double dx1 = v1x - u1.getX(); + double dy1 = v1y - u1.getY(); + double dz1 = v1z - u1.getZ(); + double dx2 = v2x - u2.getX(); + double dy2 = v2y - u2.getY(); + double dz2 = v2z - u2.getZ(); + Vector3D k = new Vector3D(dy1 * dz2 - dz1 * dy2, + dz1 * dx2 - dx1 * dz2, + dx1 * dy2 - dy1 * dx2); + double c = k.getX() * (u1y * u2z - u1z * u2y) + + k.getY() * (u1z * u2x - u1x * u2z) + + k.getZ() * (u1x * u2y - u1y * u2x); + + if (c < (1.0e-10 * u1u1 * u2u2)) { + // the (q1, q2, q3) vector is in the (u1, u2) plane + // we try other vectors + Vector3D u3 = Vector3D.crossProduct(u1, u2); + Vector3D v3 = Vector3D.crossProduct(v1, v2); + double u3x = u3.getX(); + double u3y = u3.getY(); + double u3z = u3.getZ(); + double v3x = v3.getX(); + double v3y = v3.getY(); + double v3z = v3.getZ(); + double u3u3 = u1u1 * u2u2 - u1u2 * u1u2; + + double dx3 = v3x - u3x; + double dy3 = v3y - u3y; + double dz3 = v3z - u3z; + k = new Vector3D(dy1 * dz3 - dz1 * dy3, + dz1 * dx3 - dx1 * dz3, + dx1 * dy3 - dy1 * dx3); + c = k.getX() * (u1y * u3z - u1z * u3y) + + k.getY() * (u1z * u3x - u1x * u3z) + + k.getZ() * (u1x * u3y - u1y * u3x); + + if (c < (1.0e-10 * u1u1 * u3u3)) { + // the (q1, q2, q3) vector is aligned with u1: + // we try (u2, u3) and (v2, v3) + k = new Vector3D(dy2 * dz3 - dz2 * dy3, + dz2 * dx3 - dx2 * dz3, + dx2 * dy3 - dy2 * dx3); + c = k.getX() * (u2y * u3z - u2z * u3y) + + k.getY() * (u2z * u3x - u2x * u3z) + + k.getZ() * (u2x * u3y - u2y * u3x); + + if (c < (1.0e-10 * u2u2 * u3u3)) { + // the (q1, q2, q3) vector is aligned with everything + // this is really the identity rotation + q0 = 1.0; + q1 = 0.0; + q2 = 0.0; + q3 = 0.0; + return; + } + + // we will have to use u2 and v2 to compute the scalar part + uRef = u2; + vRef = v2; + + } + + } + + // compute the vectorial part + c = Math.sqrt(c); + double inv = 1.0 / (c + c); + q1 = inv * k.getX(); + q2 = inv * k.getY(); + q3 = inv * k.getZ(); + + // compute the scalar part + k = new Vector3D(uRef.getY() * q3 - uRef.getZ() * q2, + uRef.getZ() * q1 - uRef.getX() * q3, + uRef.getX() * q2 - uRef.getY() * q1); + c = Vector3D.dotProduct(k, k); + q0 = Vector3D.dotProduct(vRef, k) / (c + c); + + } + + /** Build one of the rotations that transform one vector into another one. + + *

    Except for a possible scale factor, if the instance were + * applied to the vector u it will produce the vector v. There is an + * infinite number of such rotations, this constructor choose the + * one with the smallest associated angle (i.e. the one whose axis + * is orthogonal to the (u, v) plane). If u and v are colinear, an + * arbitrary rotation axis is chosen.

    + + * @param u origin vector + * @param v desired image of u by the rotation + * @exception ArithmeticException if the norm of one of the vectors is null + */ + public Rotation(Vector3D u, Vector3D v) { + + double normProduct = u.getNorm() * v.getNorm(); + if (normProduct < 1.0e-15) { + throw new ArithmeticException("null norm"); + } + + double dot = Vector3D.dotProduct(u, v); + + if (dot < ((2.0e-15 - 1.0) * normProduct)) { + // special case u = -v: we select a PI angle rotation around + // an arbitrary vector orthogonal to u + Vector3D w = u.orthogonal(); + q0 = 0.0; + q1 = -w.getX(); + q2 = -w.getY(); + q3 = -w.getZ(); + } else { + // general case: (u, v) defines a plane, we select + // the shortest possible rotation: axis orthogonal to this plane + q0 = Math.sqrt(0.5 * (1.0 + dot / normProduct)); + double coeff = 1.0 / (2.0 * q0 * normProduct); + q1 = coeff * (v.getY() * u.getZ() - v.getZ() * u.getY()); + q2 = coeff * (v.getZ() * u.getX() - v.getX() * u.getZ()); + q3 = coeff * (v.getX() * u.getY() - v.getY() * u.getX()); + } + + } + + /** Build a rotation from three Cardan or Euler elementary rotations. + + *

    Cardan rotations are three successive rotations around the + * canonical axes X, Y and Z, each axis beeing used once. There are + * 6 such sets of rotations (XYZ, XZY, YXZ, YZX, ZXY and ZYX). Euler + * rotations are three successive rotations around the canonical + * axes X, Y and Z, the first and last rotations beeing around the + * same axis. There are 6 such sets of rotations (XYX, XZX, YXY, + * YZY, ZXZ and ZYZ), the most popular one being ZXZ. Beware that + * many people routinely use the term Euler angles even for what + * really are Cardan angles (this confusion is especially widespread + * in the aerospace business where Roll, Pitch and Yaw angles are + * often tagged as Euler angles).

    + + * @param order order of rotations to use + * @param alpha1 angle of the first elementary rotation + * @param alpha2 angle of the second elementary rotation + * @param alpha3 angle of the third elementary rotation + */ + public Rotation(RotationOrder order, + double alpha1, double alpha2, double alpha3) { + + if (order == RotationOrder.XYZ) { + + compose(new Rotation(Vector3D.plusI, alpha1), + new Rotation(Vector3D.plusJ, alpha2), + new Rotation(Vector3D.plusK, alpha3)); + + } else if (order == RotationOrder.XZY) { + + compose(new Rotation(Vector3D.plusI, alpha1), + new Rotation(Vector3D.plusK, alpha2), + new Rotation(Vector3D.plusJ, alpha3)); + + } else if (order == RotationOrder.YXZ) { + + compose(new Rotation(Vector3D.plusJ, alpha1), + new Rotation(Vector3D.plusI, alpha2), + new Rotation(Vector3D.plusK, alpha3)); + + } else if (order == RotationOrder.YZX) { + + compose(new Rotation(Vector3D.plusJ, alpha1), + new Rotation(Vector3D.plusK, alpha2), + new Rotation(Vector3D.plusI, alpha3)); + + } else if (order == RotationOrder.ZXY) { + + compose(new Rotation(Vector3D.plusK, alpha1), + new Rotation(Vector3D.plusI, alpha2), + new Rotation(Vector3D.plusJ, alpha3)); + + } else if (order == RotationOrder.ZYX) { + + compose(new Rotation(Vector3D.plusK, alpha1), + new Rotation(Vector3D.plusJ, alpha2), + new Rotation(Vector3D.plusI, alpha3)); + + } else if (order == RotationOrder.XYX) { + + compose(new Rotation(Vector3D.plusI, alpha1), + new Rotation(Vector3D.plusJ, alpha2), + new Rotation(Vector3D.plusI, alpha3)); + + } else if (order == RotationOrder.XZX) { + + compose(new Rotation(Vector3D.plusI, alpha1), + new Rotation(Vector3D.plusK, alpha2), + new Rotation(Vector3D.plusI, alpha3)); + + } else if (order == RotationOrder.YXY) { + + compose(new Rotation(Vector3D.plusJ, alpha1), + new Rotation(Vector3D.plusI, alpha2), + new Rotation(Vector3D.plusJ, alpha3)); + + } else if (order == RotationOrder.YZY) { + + compose(new Rotation(Vector3D.plusJ, alpha1), + new Rotation(Vector3D.plusK, alpha2), + new Rotation(Vector3D.plusJ, alpha3)); + + } else if (order == RotationOrder.ZXZ) { + + compose(new Rotation(Vector3D.plusK, alpha1), + new Rotation(Vector3D.plusI, alpha2), + new Rotation(Vector3D.plusK, alpha3)); + + } else { // last possibility is ZYZ + + compose(new Rotation(Vector3D.plusK, alpha1), + new Rotation(Vector3D.plusJ, alpha2), + new Rotation(Vector3D.plusK, alpha3)); + + } + + } + + /** Override the instance by the composition of three rotations. + * @param r1 last (outermost) rotation to compose + * @param r2 intermediate rotation to compose + * @param r3 first (innermost) rotation to compose + */ + private void compose(Rotation r1, Rotation r2, Rotation r3) { + Rotation composed = r1.applyTo(r2.applyTo(r3)); + q0 = composed.q0; + q1 = composed.q1; + q2 = composed.q2; + q3 = composed.q3; + } + + /** Copy constructor. + * Build a copy of a rotation + * @param r rotation to copy + */ + public Rotation(Rotation r) { + q0 = r.q0; + q1 = r.q1; + q2 = r.q2; + q3 = r.q3; + } + + /** Revert a rotation. + * Build a rotation which reverse the effect of another + * rotation. This means that is r(u) = v, then r.revert (v) = u. The + * instance is not changed. + * @return a new rotation whose effect is the reverse of the effect + * of the instance + */ + public Rotation revert() { + return new Rotation(-q0, q1, q2, q3); + } + + /** Get the scalar coordinate of the quaternion. + * @return scalar coordinate of the quaternion + */ + public double getQ0() { + return q0; + } + + /** Get the first coordinate of the vectorial part of the quaternion. + * @return first coordinate of the vectorial part of the quaternion + */ + public double getQ1() { + return q1; + } + + /** Get the second coordinate of the vectorial part of the quaternion. + * @return second coordinate of the vectorial part of the quaternion + */ + public double getQ2() { + return q2; + } + + /** Get the third coordinate of the vectorial part of the quaternion. + * @return third coordinate of the vectorial part of the quaternion + */ + public double getQ3() { + return q3; + } + + /** Get the normalized axis of the rotation. + * @return normalized axis of the rotation + */ + public Vector3D getAxis() { + double squaredSine = q1 * q1 + q2 * q2 + q3 * q3; + if (squaredSine < 1.0e-12) { + return new Vector3D(1, 0, 0); + } else if (q0 < 0) { + double inverse = 1 / Math.sqrt(squaredSine); + return new Vector3D(q1 * inverse, q2 * inverse, q3 * inverse); + } else { + double inverse = -1 / Math.sqrt(squaredSine); + return new Vector3D(q1 * inverse, q2 * inverse, q3 * inverse); + } + } + + /** Get the angle of the rotation. + * @return angle of the rotation (between 0 and PI) + */ + public double getAngle() { + if ((q0 < -0.1) || (q0 > 0.1)) { + return 2 * Math.asin(Math.sqrt(q1 * q1 + q2 * q2 + q3 * q3)); + } else if (q0 < 0) { + return 2 * Math.acos(-q0); + } else { + return 2 * Math.acos(q0); + } + } + + /** Get the Cardan or Euler angles corresponding to the instance. + + *

    The equations show that each rotation can be defined by two + * different values of the Cardan or Euler angles set. For example + * if Cardan angles are used, the rotation defined by the angles a1, + * a2 and a3 is the same as the rotation defined by the angles PI + + * a1, PI - a2 and PI + a3. This method implements the following + * arbitrary choices. For Cardan angles, the chosen set is the one + * for which the second angle is between -PI/2 and PI/2 (i.e its + * cosine is positive). For Euler angles, the chosen set is the one + * for which the second angle is between 0 and PI (i.e its sine is + * positive).

    + + *

    Cardan and Euler angle have a very disappointing drawback: all + * of them have singularities. This means that if the instance is + * too close to the singularities corresponding to the given + * rotation order, it will be impossible to retrieve the angles. For + * Cardan angles, this is often called gimbal lock. There is + * nothing to do to prevent this, it is an intrisic problem + * of Cardan and Euler representation (but not a problem with the + * rotation itself, which is perfectly well defined). For Cardan + * angles, singularities occur when the second angle is close to + * -PI/2 or +PI/2, for Euler angle singularities occur when the + * second angle is close to 0 or PI, this means that the identity + * rotation is always singular for Euler angles ! + + * @param order rotation order to use + * @return an array of three angles, in the order specified by the set + * @exception CardanEulerSingularityException if the rotation is + * singular with respect to the angles set specified + */ + public double[] getAngles(RotationOrder order) + throws CardanEulerSingularityException { + + final double small = 1.0e-10; + final double maxThreshold = 1.0 - small; + final double minThreshold = -maxThreshold; + + double[] angles = new double[3]; + Vector3D v1 = null; + Vector3D v2 = null; + + if (order == RotationOrder.XYZ) { + + // r (Vector3D.plusK) coordinates are : + // sin (theta), -cos (theta) sin (phi), cos (theta) cos (phi) + // (-r) (Vector3D.plusI) coordinates are : + // cos (psi) cos (theta), -sin (psi) cos (theta), sin (theta) + // and we can choose to have theta in the interval [-PI/2 ; +PI/2] + v1 = applyTo(Vector3D.plusK); + v2 = applyInverseTo(Vector3D.plusI); + if ((v2.getZ() < minThreshold) || (v2.getZ() > maxThreshold)) { + throw new CardanEulerSingularityException(true); + } + angles[0] = Math.atan2(-(v1.getY()), v1.getZ()); + angles[1] = Math.asin(v2.getZ()); + angles[2] = Math.atan2(-(v2.getY()), v2.getX()); + + } else if (order == RotationOrder.XZY) { + + // r (Vector3D.plusJ) coordinates are : + // -sin (psi), cos (psi) cos (phi), cos (psi) sin (phi) + // (-r) (Vector3D.plusI) coordinates are : + // cos (theta) cos (psi), -sin (psi), sin (theta) cos (psi) + // and we can choose to have psi in the interval [-PI/2 ; +PI/2] + v1 = applyTo(Vector3D.plusJ); + v2 = applyInverseTo(Vector3D.plusI); + if ((v2.getY() < minThreshold) || (v2.getY() > maxThreshold)) { + throw new CardanEulerSingularityException(true); + } + angles[0] = Math.atan2(v1.getZ(), v1.getY()); + angles[1] = -Math.asin(v2.getY()); + angles[2] = Math.atan2(v2.getZ(), v2.getX()); + + } else if (order == RotationOrder.YXZ) { + + // r (Vector3D.plusK) coordinates are : + // cos (phi) sin (theta), -sin (phi), cos (phi) cos (theta) + // (-r) (Vector3D.plusJ) coordinates are : + // sin (psi) cos (phi), cos (psi) cos (phi), -sin (phi) + // and we can choose to have phi in the interval [-PI/2 ; +PI/2] + v1 = applyTo(Vector3D.plusK); + v2 = applyInverseTo(Vector3D.plusJ); + if ((v2.getZ() < minThreshold) || (v2.getZ() > maxThreshold)) { + throw new CardanEulerSingularityException(true); + } + angles[0] = Math.atan2(v1.getX(), v1.getZ()); + angles[1] = -Math.asin(v2.getZ()); + angles[2] = Math.atan2(v2.getX(), v2.getY()); + + } else if (order == RotationOrder.YZX) { + + // r (Vector3D.plusI) coordinates are : + // cos (psi) cos (theta), sin (psi), -cos (psi) sin (theta) + // (-r) (Vector3D.plusJ) coordinates are : + // sin (psi), cos (phi) cos (psi), -sin (phi) cos (psi) + // and we can choose to have psi in the interval [-PI/2 ; +PI/2] + v1 = applyTo(Vector3D.plusI); + v2 = applyInverseTo(Vector3D.plusJ); + if ((v2.getX() < minThreshold) || (v2.getX() > maxThreshold)) { + throw new CardanEulerSingularityException(true); + } + angles[0] = Math.atan2(-(v1.getZ()), v1.getX()); + angles[1] = Math.asin(v2.getX()); + angles[2] = Math.atan2(-(v2.getZ()), v2.getY()); + + } else if (order == RotationOrder.ZXY) { + + // r (Vector3D.plusJ) coordinates are : + // -cos (phi) sin (psi), cos (phi) cos (psi), sin (phi) + // (-r) (Vector3D.plusK) coordinates are : + // -sin (theta) cos (phi), sin (phi), cos (theta) cos (phi) + // and we can choose to have phi in the interval [-PI/2 ; +PI/2] + v1 = applyTo(Vector3D.plusJ); + v2 = applyInverseTo(Vector3D.plusK); + if ((v2.getY() < minThreshold) || (v2.getY() > maxThreshold)) { + throw new CardanEulerSingularityException(true); + } + angles[0] = Math.atan2(-(v1.getX()), v1.getY()); + angles[1] = Math.asin(v2.getY()); + angles[2] = Math.atan2(-(v2.getX()), v2.getZ()); + + } else if (order == RotationOrder.ZYX) { + + // r (Vector3D.plusI) coordinates are : + // cos (theta) cos (psi), cos (theta) sin (psi), -sin (theta) + // (-r) (Vector3D.plusK) coordinates are : + // -sin (theta), sin (phi) cos (theta), cos (phi) cos (theta) + // and we can choose to have theta in the interval [-PI/2 ; +PI/2] + v1 = applyTo(Vector3D.plusI); + v2 = applyInverseTo(Vector3D.plusK); + if ((v2.getX() < minThreshold) || (v2.getX() > maxThreshold)) { + throw new CardanEulerSingularityException(true); + } + angles[0] = Math.atan2(v1.getY(), v1.getX()); + angles[1] = -Math.asin(v2.getX()); + angles[2] = Math.atan2(v2.getY(), v2.getZ()); + + } else if (order == RotationOrder.XYX) { + + // r (Vector3D.plusI) coordinates are : + // cos (theta), sin (phi1) sin (theta), -cos (phi1) sin (theta) + // (-r) (Vector3D.plusI) coordinates are : + // cos (theta), sin (theta) sin (phi2), sin (theta) cos (phi2) + // and we can choose to have theta in the interval [0 ; PI] + v1 = applyTo(Vector3D.plusI); + v2 = applyInverseTo(Vector3D.plusI); + if ((v2.getX() < minThreshold) || (v2.getX() > maxThreshold)) { + throw new CardanEulerSingularityException(false); + } + angles[0] = Math.atan2(v1.getY(), -v1.getZ()); + angles[1] = Math.acos(v2.getX()); + angles[2] = Math.atan2(v2.getY(), v2.getZ()); + + } else if (order == RotationOrder.XZX) { + + // r (Vector3D.plusI) coordinates are : + // cos (psi), cos (phi1) sin (psi), sin (phi1) sin (psi) + // (-r) (Vector3D.plusI) coordinates are : + // cos (psi), -sin (psi) cos (phi2), sin (psi) sin (phi2) + // and we can choose to have psi in the interval [0 ; PI] + v1 = applyTo(Vector3D.plusI); + v2 = applyInverseTo(Vector3D.plusI); + if ((v2.getX() < minThreshold) || (v2.getX() > maxThreshold)) { + throw new CardanEulerSingularityException(false); + } + angles[0] = Math.atan2(v1.getZ(), v1.getY()); + angles[1] = Math.acos(v2.getX()); + angles[2] = Math.atan2(v2.getZ(), -v2.getY()); + + } else if (order == RotationOrder.YXY) { + + // r (Vector3D.plusJ) coordinates are : + // sin (theta1) sin (phi), cos (phi), cos (theta1) sin (phi) + // (-r) (Vector3D.plusJ) coordinates are : + // sin (phi) sin (theta2), cos (phi), -sin (phi) cos (theta2) + // and we can choose to have phi in the interval [0 ; PI] + v1 = applyTo(Vector3D.plusJ); + v2 = applyInverseTo(Vector3D.plusJ); + if ((v2.getY() < minThreshold) || (v2.getY() > maxThreshold)) { + throw new CardanEulerSingularityException(false); + } + angles[0] = Math.atan2(v1.getX(), v1.getZ()); + angles[1] = Math.acos(v2.getY()); + angles[2] = Math.atan2(v2.getX(), -v2.getZ()); + + } else if (order == RotationOrder.YZY) { + + // r (Vector3D.plusJ) coordinates are : + // -cos (theta1) sin (psi), cos (psi), sin (theta1) sin (psi) + // (-r) (Vector3D.plusJ) coordinates are : + // sin (psi) cos (theta2), cos (psi), sin (psi) sin (theta2) + // and we can choose to have psi in the interval [0 ; PI] + v1 = applyTo(Vector3D.plusJ); + v2 = applyInverseTo(Vector3D.plusJ); + if ((v2.getY() < minThreshold) || (v2.getY() > maxThreshold)) { + throw new CardanEulerSingularityException(false); + } + angles[0] = Math.atan2(v1.getZ(), -v1.getX()); + angles[1] = Math.acos(v2.getY()); + angles[2] = Math.atan2(v2.getZ(), v2.getX()); + + } else if (order == RotationOrder.ZXZ) { + + // r (Vector3D.plusK) coordinates are : + // sin (psi1) sin (phi), -cos (psi1) sin (phi), cos (phi) + // (-r) (Vector3D.plusK) coordinates are : + // sin (phi) sin (psi2), sin (phi) cos (psi2), cos (phi) + // and we can choose to have phi in the interval [0 ; PI] + v1 = applyTo(Vector3D.plusK); + v2 = applyInverseTo(Vector3D.plusK); + if ((v2.getZ() < minThreshold) || (v2.getZ() > maxThreshold)) { + throw new CardanEulerSingularityException(false); + } + angles[0] = Math.atan2(v1.getX(), -v1.getY()); + angles[1] = Math.acos(v2.getZ()); + angles[2] = Math.atan2(v2.getX(), v2.getY()); + + } else { // last possibility is ZYZ + + // r (Vector3D.plusK) coordinates are : + // cos (psi1) sin (theta), sin (psi1) sin (theta), cos (theta) + // (-r) (Vector3D.plusK) coordinates are : + // -sin (theta) cos (psi2), sin (theta) sin (psi2), cos (theta) + // and we can choose to have theta in the interval [0 ; PI] + v1 = applyTo(Vector3D.plusK); + v2 = applyInverseTo(Vector3D.plusK); + if ((v2.getZ() < minThreshold) || (v2.getZ() > maxThreshold)) { + throw new CardanEulerSingularityException(false); + } + angles[0] = Math.atan2(v1.getY(), v1.getX()); + angles[1] = Math.acos(v2.getZ()); + angles[2] = Math.atan2(v2.getY(), -v2.getX()); + + } + + return angles; + + } + + /** Get the 3X3 matrix corresponding to the instance + * @return the matrix corresponding to the instance + */ + public double[][] getMatrix() { + + // products + double q0q0 = q0 * q0; + double q0q1 = q0 * q1; + double q0q2 = q0 * q2; + double q0q3 = q0 * q3; + double q1q1 = q1 * q1; + double q1q2 = q1 * q2; + double q1q3 = q1 * q3; + double q2q2 = q2 * q2; + double q2q3 = q2 * q3; + double q3q3 = q3 * q3; + + // create the matrix + double[][] m = new double[3][]; + m[0] = new double[3]; + m[1] = new double[3]; + m[2] = new double[3]; + + m [0][0] = 2.0 * (q0q0 + q1q1) - 1.0; + m [1][0] = 2.0 * (q1q2 - q0q3); + m [2][0] = 2.0 * (q1q3 + q0q2); + + m [0][1] = 2.0 * (q1q2 + q0q3); + m [1][1] = 2.0 * (q0q0 + q2q2) - 1.0; + m [2][1] = 2.0 * (q2q3 - q0q1); + + m [0][2] = 2.0 * (q1q3 - q0q2); + m [1][2] = 2.0 * (q2q3 + q0q1); + m [2][2] = 2.0 * (q0q0 + q3q3) - 1.0; + + return m; + + } + + /** Apply the rotation to a vector. + * @param u vector to apply the rotation to + * @return a new vector which is the image of u by the rotation + */ + public Vector3D applyTo(Vector3D u) { + + double x = u.getX(); + double y = u.getY(); + double z = u.getZ(); + + double s = q1 * x + q2 * y + q3 * z; + + return new Vector3D(2 * (q0 * (x * q0 - (q2 * z - q3 * y)) + s * q1) - x, + 2 * (q0 * (y * q0 - (q3 * x - q1 * z)) + s * q2) - y, + 2 * (q0 * (z * q0 - (q1 * y - q2 * x)) + s * q3) - z); + + } + + /** Apply the inverse of the rotation to a vector. + * @param u vector to apply the inverse of the rotation to + * @return a new vector which such that u is its image by the rotation + */ + public Vector3D applyInverseTo(Vector3D u) { + + double x = u.getX(); + double y = u.getY(); + double z = u.getZ(); + + double s = q1 * x + q2 * y + q3 * z; + double m0 = -q0; + + return new Vector3D(2 * (m0 * (x * m0 - (q2 * z - q3 * y)) + s * q1) - x, + 2 * (m0 * (y * m0 - (q3 * x - q1 * z)) + s * q2) - y, + 2 * (m0 * (z * m0 - (q1 * y - q2 * x)) + s * q3) - z); + + } + + /** Apply the instance to another rotation. + * Applying the instance to a rotation is computing the composition + * in an order compliant with the following rule : let u be any + * vector and v its image by r (i.e. r.applyTo(u) = v), let w be the image + * of v by the instance (i.e. applyTo(v) = w), then w = comp.applyTo(u), + * where comp = applyTo(r). + * @param r rotation to apply the rotation to + * @return a new rotation which is the composition of r by the instance + */ + public Rotation applyTo(Rotation r) { + return new Rotation(r.q0 * q0 - (r.q1 * q1 + r.q2 * q2 + r.q3 * q3), + r.q1 * q0 + r.q0 * q1 + (r.q2 * q3 - r.q3 * q2), + r.q2 * q0 + r.q0 * q2 + (r.q3 * q1 - r.q1 * q3), + r.q3 * q0 + r.q0 * q3 + (r.q1 * q2 - r.q2 * q1)); + } + + /** Apply the inverse of the instance to another rotation. + * Applying the inverse of the instance to a rotation is computing + * the composition in an order compliant with the following rule : + * let u be any vector and v its image by r (i.e. r.applyTo(u) = v), + * let w be the inverse image of v by the instance + * (i.e. applyInverseTo(v) = w), then w = comp.applyTo(u), where + * comp = applyInverseTo(r). + * @param r rotation to apply the rotation to + * @return a new rotation which is the composition of r by the inverse + * of the instance + */ + public Rotation applyInverseTo(Rotation r) { + return new Rotation(-r.q0 * q0 - (r.q1 * q1 + r.q2 * q2 + r.q3 * q3), + -r.q1 * q0 + r.q0 * q1 + (r.q2 * q3 - r.q3 * q2), + -r.q2 * q0 + r.q0 * q2 + (r.q3 * q1 - r.q1 * q3), + -r.q3 * q0 + r.q0 * q3 + (r.q1 * q2 - r.q2 * q1)); + } + + /** Perfect orthogonality on a 3X3 matrix. + * @param m initial matrix (not exactly orthogonal) + * @param threshold convergence threshold for the iterative + * orthogonality correction (convergence is reached when the + * difference between two steps of the Frobenius norm of the + * correction is below this threshold) + * @return an orthogonal matrix close to m + * @exception NotARotationMatrixException if the matrix cannot be + * orthogonalized with the given threshold after 10 iterations + */ + private double[][] orthogonalizeMatrix(double[][] m, double threshold) + throws NotARotationMatrixException { + double x00 = m[0][0]; + double x01 = m[0][1]; + double x02 = m[0][2]; + double x10 = m[1][0]; + double x11 = m[1][1]; + double x12 = m[1][2]; + double x20 = m[2][0]; + double x21 = m[2][1]; + double x22 = m[2][2]; + double fn = 0; + double fn1; + + double[][] o = new double[3][]; + o[0] = new double[3]; + o[1] = new double[3]; + o[2] = new double[3]; + + // iterative correction: Xn+1 = Xn - 0.5 * (Xn.Mt.Xn - M) + int i = 0; + while (++i < 11) { + + // Mt.Xn + double mx00 = m[0][0] * x00 + m[1][0] * x10 + m[2][0] * x20; + double mx10 = m[0][1] * x00 + m[1][1] * x10 + m[2][1] * x20; + double mx20 = m[0][2] * x00 + m[1][2] * x10 + m[2][2] * x20; + double mx01 = m[0][0] * x01 + m[1][0] * x11 + m[2][0] * x21; + double mx11 = m[0][1] * x01 + m[1][1] * x11 + m[2][1] * x21; + double mx21 = m[0][2] * x01 + m[1][2] * x11 + m[2][2] * x21; + double mx02 = m[0][0] * x02 + m[1][0] * x12 + m[2][0] * x22; + double mx12 = m[0][1] * x02 + m[1][1] * x12 + m[2][1] * x22; + double mx22 = m[0][2] * x02 + m[1][2] * x12 + m[2][2] * x22; + + // Xn+1 + o[0][0] = x00 - 0.5 * (x00 * mx00 + x01 * mx10 + x02 * mx20 - m[0][0]); + o[0][1] = x01 - 0.5 * (x00 * mx01 + x01 * mx11 + x02 * mx21 - m[0][1]); + o[0][2] = x02 - 0.5 * (x00 * mx02 + x01 * mx12 + x02 * mx22 - m[0][2]); + o[1][0] = x10 - 0.5 * (x10 * mx00 + x11 * mx10 + x12 * mx20 - m[1][0]); + o[1][1] = x11 - 0.5 * (x10 * mx01 + x11 * mx11 + x12 * mx21 - m[1][1]); + o[1][2] = x12 - 0.5 * (x10 * mx02 + x11 * mx12 + x12 * mx22 - m[1][2]); + o[2][0] = x20 - 0.5 * (x20 * mx00 + x21 * mx10 + x22 * mx20 - m[2][0]); + o[2][1] = x21 - 0.5 * (x20 * mx01 + x21 * mx11 + x22 * mx21 - m[2][1]); + o[2][2] = x22 - 0.5 * (x20 * mx02 + x21 * mx12 + x22 * mx22 - m[2][2]); + + // correction on each elements + double corr00 = o[0][0] - m[0][0]; + double corr01 = o[0][1] - m[0][1]; + double corr02 = o[0][2] - m[0][2]; + double corr10 = o[1][0] - m[1][0]; + double corr11 = o[1][1] - m[1][1]; + double corr12 = o[1][2] - m[1][2]; + double corr20 = o[2][0] - m[2][0]; + double corr21 = o[2][1] - m[2][1]; + double corr22 = o[2][2] - m[2][2]; + + // Frobenius norm of the correction + fn1 = corr00 * corr00 + corr01 * corr01 + corr02 * corr02 + + corr10 * corr10 + corr11 * corr11 + corr12 * corr12 + + corr20 * corr20 + corr21 * corr21 + corr22 * corr22; + + // convergence test + if (Math.abs(fn1 - fn) <= threshold) + return o; + + // prepare next iteration + x00 = o[0][0]; + x01 = o[0][1]; + x02 = o[0][2]; + x10 = o[1][0]; + x11 = o[1][1]; + x12 = o[1][2]; + x20 = o[2][0]; + x21 = o[2][1]; + x22 = o[2][2]; + fn = fn1; + + } + + // the algorithm did not converge after 10 iterations + throw new NotARotationMatrixException("unable to orthogonalize matrix" + + " in {0} iterations", + new String[] { + Integer.toString(i - 1) + }); + } + + public int getStateDimension() { + return 4; + } + + public void mapStateFromArray(int start, double[] array) { + q0 = array[start]; + q1 = array[start + 1]; + q2 = array[start + 2]; + q3 = array[start + 3]; + } + + public void mapStateToArray(int start, double[] array) { + array[start] = q0; + array[start + 1] = q1; + array[start + 2] = q2; + array[start + 3] = q3; + } + + /** Scalar coordinate of the quaternion. */ + private double q0; + + /** First coordinate of the vectorial part of the quaternion. */ + private double q1; + + /** Second coordinate of the vectorial part of the quaternion. */ + private double q2; + + /** Third coordinate of the vectorial part of the quaternion. */ + private double q3; + + private static final long serialVersionUID = 7264384082212242475L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/geometry/RotationOrder.java b/src/mantissa/src/org/spaceroots/mantissa/geometry/RotationOrder.java new file mode 100644 index 000000000..8137dbe06 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/geometry/RotationOrder.java @@ -0,0 +1,126 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.geometry; + +/** + * This class is a utility representing a rotation order specification + * for Cardan or Euler angles specification. + + * This class cannot be instanciated by the user. He can only use one + * of the twelve predefined supported orders as an argument to either + * the {@link Rotation#Rotation(RotationOrder,double,double,double)} + * constructor or the {@link Rotation#getAngles} method. + + * @version $Id: RotationOrder.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ +public final class RotationOrder { + + /** Private constructor. + * This is a utility class that cannot be instantiated by the user, + * so its only constructor is private. + * @param name name of the rotation order + */ + private RotationOrder(String name) { + this.name = name; + } + + /** Get a string representation of the instance. + * @return a string representation of the instance (in fact, its name) + */ + public String toString() { + return name; + } + + /** Set of Cardan angles. + * this ordered set of rotations is around X, then around Y, then + * around Z + */ + public static final RotationOrder XYZ = new RotationOrder("XYZ"); + + /** Set of Cardan angles. + * this ordered set of rotations is around X, then around Z, then + * around Y + */ + public static final RotationOrder XZY = new RotationOrder("XZY"); + + /** Set of Cardan angles. + * this ordered set of rotations is around Y, then around X, then + * around Z + */ + public static final RotationOrder YXZ = new RotationOrder("YXZ"); + + /** Set of Cardan angles. + * this ordered set of rotations is around Y, then around Z, then + * around X + */ + public static final RotationOrder YZX = new RotationOrder("YZX"); + + /** Set of Cardan angles. + * this ordered set of rotations is around Z, then around X, then + * around Y + */ + public static final RotationOrder ZXY = new RotationOrder("ZXY"); + + /** Set of Cardan angles. + * this ordered set of rotations is around Z, then around Y, then + * around X + */ + public static final RotationOrder ZYX = new RotationOrder("ZYX"); + + /** Set of Euler angles. + * this ordered set of rotations is around X, then around Y, then + * around X + */ + public static final RotationOrder XYX = new RotationOrder("XYX"); + + /** Set of Euler angles. + * this ordered set of rotations is around X, then around Z, then + * around X + */ + public static final RotationOrder XZX = new RotationOrder("XZX"); + + /** Set of Euler angles. + * this ordered set of rotations is around Y, then around X, then + * around Y + */ + public static final RotationOrder YXY = new RotationOrder("YXY"); + + /** Set of Euler angles. + * this ordered set of rotations is around Y, then around Z, then + * around Y + */ + public static final RotationOrder YZY = new RotationOrder("YZY"); + + /** Set of Euler angles. + * this ordered set of rotations is around Z, then around X, then + * around Z + */ + public static final RotationOrder ZXZ = new RotationOrder("ZXZ"); + + /** Set of Euler angles. + * this ordered set of rotations is around Z, then around Y, then + * around Z + */ + public static final RotationOrder ZYZ = new RotationOrder("ZYZ"); + + /** Name of the rotations order. */ + private final String name; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/geometry/Vector3D.java b/src/mantissa/src/org/spaceroots/mantissa/geometry/Vector3D.java new file mode 100644 index 000000000..8bd96d953 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/geometry/Vector3D.java @@ -0,0 +1,491 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.geometry; + +import java.io.Serializable; +import org.spaceroots.mantissa.utilities.ArraySliceMappable; + + +/** This class implements vectors in a three-dimensional space. + * @version $Id: Vector3D.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + */ + +public class Vector3D + implements ArraySliceMappable, Serializable { + + /** First canonical vector (coordinates : 1, 0, 0). + * This is really an {@link ImmutableVector3D ImmutableVector3D}, + * hence it can't be changed in any way. + */ + public static final Vector3D plusI = new ImmutableVector3D(1, 0, 0); + + /** Opposite of the first canonical vector (coordinates : -1, 0, 0). + * This is really an {@link ImmutableVector3D ImmutableVector3D}, + * hence it can't be changed in any way. + */ + public static final Vector3D minusI = new ImmutableVector3D(-1, 0, 0); + + /** Second canonical vector (coordinates : 0, 1, 0). + * This is really an {@link ImmutableVector3D ImmutableVector3D}, + * hence it can't be changed in any way. + */ + public static final Vector3D plusJ = new ImmutableVector3D(0, 1, 0); + + /** Opposite of the second canonical vector (coordinates : 0, -1, 0). + * This is really an {@link ImmutableVector3D ImmutableVector3D}, + * hence it can't be changed in any way. + */ + public static final Vector3D minusJ = new ImmutableVector3D(0, -1, 0); + + /** Third canonical vector (coordinates : 0, 0, 1). + * This is really an {@link ImmutableVector3D ImmutableVector3D}, + * hence it can't be changed in any way. + */ + public static final Vector3D plusK = new ImmutableVector3D(0, 0, 1); + + /** Opposite of the third canonical vector (coordinates : 0, 0, -1). + * This is really an {@link ImmutableVector3D ImmutableVector3D}, + * hence it can't be changed in any way. + */ + public static final Vector3D minusK = new ImmutableVector3D(0, 0, -1); + + /** Simple constructor. + * Build a null vector. + */ + public Vector3D() { + x = 0; + y = 0; + z = 0; + } + + /** Simple constructor. + * Build a vector from its coordinates + * @param x abscissa + * @param y ordinate + * @param z height + * @see #getX() + * @see #getY() + * @see #getZ() + */ + public Vector3D(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + /** Simple constructor. + * Build a vector from its azimuthal coordinates + * @param alpha azimuth (α) around Z + * (0 is +X, π/2 is +Y, π is -X and 3π/2 is -Y) + * @param delta elevation (δ) above (XY) plane, from -π/2 to +π/2 + * @see #getAlpha() + * @see #getDelta() + */ + public Vector3D(double alpha, double delta) { + double cosDelta = Math.cos(delta); + this.x = Math.cos(alpha) * cosDelta; + this.y = Math.sin(alpha) * cosDelta; + this.z = Math.sin(delta); + } + + /** Multiplicative constructor + * Build a vector from another one and a scale factor. + * The vector built will be a * u + * @param a scale factor + * @param u base (unscaled) vector + */ + public Vector3D(double a, Vector3D u) { + this.x = a * u.x; + this.y = a * u.y; + this.z = a * u.z; + } + + /** Linear constructor + * Build a vector from two other ones and corresponding scale factors. + * The vector built will be a * u + b * v + * @param a first scale factor + * @param u first base (unscaled) vector + * @param b second scale factor + * @param v second base (unscaled) vector + */ + public Vector3D(double a, Vector3D u, double b, Vector3D v) { + this.x = a * u.x + b * v.x; + this.y = a * u.y + b * v.y; + this.z = a * u.z + b * v.z; + } + + /** Linear constructor + * Build a vector from three other ones and corresponding scale factors. + * The vector built will be a * u + b * v + c * w + * @param a first scale factor + * @param u first base (unscaled) vector + * @param b second scale factor + * @param v second base (unscaled) vector + * @param c third scale factor + * @param w third base (unscaled) vector + */ + public Vector3D(double a, Vector3D u, + double b, Vector3D v, + double c, Vector3D w) { + this.x = a * u.x + b * v.x + c * w.x; + this.y = a * u.y + b * v.y + c * w.y; + this.z = a * u.z + b * v.z + c * w.z; + } + + /** Copy constructor. + * Build a copy of a vector + * @param v vector to copy + */ + public Vector3D(Vector3D v) { + x = v.x; + y = v.y; + z = v.z; + } + + /** Reset the instance. + * @param v vector to copy data from + */ + public void reset(Vector3D v) { + x = v.x; + y = v.y; + z = v.z; + } + + /** Get the abscissa of the vector. + * @return abscissa of the vector + * @see #Vector3D(double, double, double) + * @see #setX(double) + */ + public double getX() { + return x; + } + + /** Set the abscissa of the vector. + * @param x new abscissa for the vector + * @see #getX() + * @see #setCoordinates(double, double, double) + */ + public void setX(double x) { + this.x = x; + } + + /** Get the ordinate of the vector. + * @return ordinate of the vector + * @see #Vector3D(double, double, double) + * @see #setY(double) + */ + public double getY() { + return y; + } + + /** Set the ordinate of the vector. + * @param y new ordinate for the vector + * @see #getY() + * @see #setCoordinates(double, double, double) + */ + public void setY(double y) { + this.y = y; + } + + /** Get the height of the vector. + * @return height of the vector + * @see #Vector3D(double, double, double) + * @see #setZ(double) + */ + public double getZ() { + return z; + } + + /** Set the height of the vector. + * @param z new height for the vector + * @see #getZ() + * @see #setCoordinates(double, double, double) + */ + public void setZ(double z) { + this.z = z; + } + + /** Set all coordinates of the vector. + * @param x new abscissa for the vector + * @param y new ordinate for the vector + * @param z new height for the vector + * @see #Vector3D(double, double, double) + */ + public void setCoordinates(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + /** Get the norm for the vector. + * @return euclidian norm for the vector + */ + public double getNorm() { + return Math.sqrt (x * x + y * y + z * z); + } + + /** Get the azimuth of the vector. + * @return azimuth (α) of the vector, between -π and +π + * @see #Vector3D(double, double) + */ + public double getAlpha() { + return Math.atan2(y, x); + } + + /** Get the elevation of the vector. + * @return elevation (δ) of the vector, between -π/2 and +π/2 + * @see #Vector3D(double, double) + */ + public double getDelta() { + return Math.asin(z / getNorm()); + } + + /** Add a vector to the instance. + * Add a vector to the instance. The instance is changed. + * @param v vector to add + */ + public void addToSelf(Vector3D v) { + x += v.x; + y += v.y; + z += v.z; + } + + /** Add a scaled vector to the instance. + * Add a scaled vector to the instance. The instance is changed. + * @param factor scale factor to apply to v before adding it + * @param v vector to add + */ + public void addToSelf(double factor, Vector3D v) { + x += factor * v.x; + y += factor * v.y; + z += factor * v.z; + } + + /** Add two vectors. + * Add two vectors and return the sum as a new vector + * @param v1 first vector + * @param v2 second vector + * @return a new vector equal to v1 + v2 + */ + public static Vector3D add(Vector3D v1, Vector3D v2) { + return new Vector3D(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z); + } + + /** Subtract a vector from the instance. + * Subtract a vector from the instance. The instance is changed. + * @param v vector to subtract + */ + public void subtractFromSelf(Vector3D v) { + x -= v.x; + y -= v.y; + z -= v.z; + } + + /** Subtract two vectors. + * Subtract two vectors and return the difference as a new vector + * @param v1 first vector + * @param v2 second vector + * @return a new vector equal to v1 - v2 + */ + public static Vector3D subtract(Vector3D v1, Vector3D v2) { + return new Vector3D(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z); + } + + /** Normalize the instance. + * Divide the instance by its norm in order to have a unit + * vector. The instance is changed. + * @exception ArithmeticException if the norm is null + */ + public void normalizeSelf() { + double s = getNorm(); + if (s == 0) { + throw new ArithmeticException("null norm"); + } + double invNorm = 1 / s; + x *= invNorm; + y *= invNorm; + z *= invNorm; + } + + /** Get a vector orthogonal to the instance. + *

    There are an infinite number of normalized vectors orthogonal + * to the instance. This method picks up one of them almost + * arbitrarily. It is useful when one needs to compute a reference + * frame with one of the axes in a predefined direction. The + * following example shows how to build a frame having the k axis + * aligned with the known vector u : + *

    
    +   *   Vector3D k = u;
    +   *   k.normalizeSelf();
    +   *   Vector3D i = k.orthogonal();
    +   *   Vector3D j = Vector3D.crossProduct(k, i);
    +   * 

    + * @return a new normalized vector orthogonal to the instance + * @exception ArithmeticException if the norm of the instance is null + */ + public Vector3D orthogonal() { + + double threshold = 0.6 * getNorm(); + if (threshold == 0) { + throw new ArithmeticException("null norm"); + } + + if ((x >= -threshold) && (x <= threshold)) { + double inverse = 1 / Math.sqrt(y * y + z * z); + return new Vector3D(0, inverse * z, -inverse * y); + } else if ((y >= -threshold) && (y <= threshold)) { + double inverse = 1 / Math.sqrt(x * x + z * z); + return new Vector3D(-inverse * z, 0, inverse * x); + } else { + double inverse = 1 / Math.sqrt(x * x + y * y); + return new Vector3D(inverse * y, -inverse * x, 0); + } + + } + + /** Compute the angular separation between two vectors. + *

    This method computes the angular separation between two + * vectors using the dot product for well separated vectors and the + * cross product for almost aligned vectors. This allow to have a + * good accuracy in all cases, even for vectors very close to each + * other.

    + * @param v1 first vector + * @param v2 second vector + * @exception ArithmeticException if either vector has a null norm + */ + public static double angle(Vector3D v1, Vector3D v2) { + + double normProduct = v1.getNorm() * v2.getNorm(); + if (normProduct == 0) { + throw new ArithmeticException("null norm"); + } + + double dot = dotProduct(v1, v2); + double threshold = normProduct * 0.9999; + if ((dot < -threshold) || (dot > threshold)) { + // the vectors are almost aligned, compute using the sine + Vector3D v3 = crossProduct(v1, v2); + if (dot >= 0) { + return Math.asin(v3.getNorm() / normProduct); + } + return Math.PI - Math.asin(v3.getNorm() / normProduct); + } + + // the vectors are sufficiently separated to use the cosine + return Math.acos(dot / normProduct); + + } + + /** Revert the instance. + * Replace the instance u by -u + */ + public void negateSelf() { + x = -x; + y = -y; + z = -z; + } + + /** Get the opposite of a vector. + * @param u vector to revert + * @return a new vector which is -u + */ + public static Vector3D negate(Vector3D u) { + return new Vector3D(-u.x, -u.y, -u.z); + } + + /** Multiply the instance by a scalar + * Multiply the instance by a scalar. The instance is changed. + * @param a scalar by which the instance should be multiplied + */ + public void multiplySelf(double a) { + x *= a; + y *= a; + z *= a; + } + + /** Multiply a vector by a scalar + * Multiply a vectors by a scalar and return the product as a new vector + * @param a scalar + * @param v vector + * @return a new vector equal to a * v + */ + public static Vector3D multiply(double a, Vector3D v) { + return new Vector3D(a * v.x, a * v.y, a * v.z); + } + + /** Compute the dot-product of two vectors. + * @param v1 first vector + * @param v2 second vector + * @return the dot product v1.v2 + */ + public static double dotProduct(Vector3D v1, Vector3D v2) { + return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; + } + + /** Set the instance to the result of the cross-product of two vectors. + * @param v1 first vector (can be the instance) + * @param v2 second vector (can be the instance) + */ + public void setToCrossProduct(Vector3D v1, Vector3D v2) { + double newX = v1.y * v2.z - v1.z * v2.y; + double newY = v1.z * v2.x - v1.x * v2.z; + z = v1.x * v2.y - v1.y * v2.x; + x = newX; + y = newY; + } + + /** Compute the cross-product of two vectors. + * @param v1 first vector + * @param v2 second vector + * @return the cross product v1 ^ v2 as a new Vector + */ + public static Vector3D crossProduct(Vector3D v1, Vector3D v2) { + return new Vector3D(v1.y * v2.z - v1.z * v2.y, + v1.z * v2.x - v1.x * v2.z, + v1.x * v2.y - v1.y * v2.x); + } + + public int getStateDimension() { + return 3; + } + + public void mapStateFromArray(int start, double[] array) { + x = array[start]; + y = array[start + 1]; + z = array[start + 2]; + } + + public void mapStateToArray(int start, double[] array) { + array[start] = x; + array[start + 1] = y; + array[start + 2] = z; + } + + /** Abscissa. */ + protected double x; + + /** Ordinate. */ + protected double y; + + /** Height. */ + protected double z; + + private static final long serialVersionUID = 4115635019045864211L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/linalg/DiagonalMatrix.java b/src/mantissa/src/org/spaceroots/mantissa/linalg/DiagonalMatrix.java new file mode 100644 index 000000000..5c75765b1 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/linalg/DiagonalMatrix.java @@ -0,0 +1,142 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.linalg; + +import java.io.Serializable; + +/** This class implements diagonal matrices of linear algebra. + + * @version $Id: DiagonalMatrix.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class DiagonalMatrix + extends SquareMatrix + implements Serializable, Cloneable { + + /** Simple constructor. + * This constructor builds a diagonal matrix of specified order, all + * elements on the diagonal being ones (so this is an identity matrix). + * @param order order of the matrix + */ + public DiagonalMatrix(int order) { + this(order, 1.0); + } + + /** Simple constructor. + * This constructor builds a diagonal matrix of specified order and + * set all diagonal elements to the same value. + * @param order order of the matrix + * @param value value for the diagonal elements + */ + public DiagonalMatrix(int order, double value) { + super(order); + for (int index = 0; index < order * order; index += order + 1) { + data[index] = value; + } + } + + /** Simple constructor. + * Build a matrix with specified elements. + * @param order order of the matrix + * @param data table of the matrix elements (stored row after row) + */ + public DiagonalMatrix(int order, double[] data) { + super(order, data); + } + + /** Copy constructor. + * @param d diagonal matrix to copy + */ + public DiagonalMatrix(DiagonalMatrix d) { + super(d); + } + + public Matrix duplicate() { + return new DiagonalMatrix(this); + } + + public void setElement(int i, int j, double value) { + if (i != j) { + throw new ArrayIndexOutOfBoundsException("cannot set elements" + + " out of diagonal in a" + + " diagonal matrix"); + } + super.setElement(i, j, value); + } + + public double getDeterminant(double epsilon) { + double determinant = data[0]; + for (int index = columns + 1; index < columns * columns; index += columns + 1) { + determinant *= data[index]; + } + return determinant; + } + + public SquareMatrix getInverse(double epsilon) + throws SingularMatrixException { + + DiagonalMatrix inv = new DiagonalMatrix (columns); + + for (int index = 0; index < columns * columns; index += columns + 1) { + if (Math.abs(data[index]) < epsilon) { + throw new SingularMatrixException(); + } + inv.data[index] = 1.0 / data[index]; + } + + return inv; + + } + + public Matrix solve(Matrix b, double epsilon) + throws SingularMatrixException { + + Matrix result = b.duplicate(); + + for (int i = 0; i < columns; ++i) { + double diag = data[i * (columns + 1)]; + if (Math.abs(diag) < epsilon) { + throw new SingularMatrixException(); + } + double inv = 1.0 / diag; + + NonNullRange range = result.getRangeForRow(i); + for (int index = i * b.columns + range.begin; + index < i * b.columns + range.end; + ++index) { + result.data[index] = inv * b.data[index]; + } + } + + return result; + + } + + public NonNullRange getRangeForRow(int i) { + return new NonNullRange(i, i + 1); + } + + public NonNullRange getRangeForColumn(int j) { + return new NonNullRange(j, j + 1); + } + + private static final long serialVersionUID = -2965166085913895323L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/linalg/GeneralMatrix.java b/src/mantissa/src/org/spaceroots/mantissa/linalg/GeneralMatrix.java new file mode 100644 index 000000000..4de264588 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/linalg/GeneralMatrix.java @@ -0,0 +1,122 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.linalg; + +import java.io.Serializable; + +/** This class represents matrices of the most general type. + + *

    This class is the basic implementation of matrices to use when + * nothing special is known about the structure of the matrix.

    + + * @version $Id: GeneralMatrix.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class GeneralMatrix + extends Matrix + implements Serializable { + + /** Simple constructor. + * Build a matrix with null elements. + * @param rows number of rows of the matrix + * @param columns number of columns of the matrix + */ + public GeneralMatrix(int rows, int columns) { + super(rows, columns); + } + + /** Simple constructor. + * Build a matrix with specified elements. + * @param rows number of rows of the matrix + * @param columns number of columns of the matrix + * @param data table of the matrix elements (stored row after row) + */ + public GeneralMatrix(int rows, int columns, double[] data) { + super(rows, columns, data); + } + + /** Copy constructor. + * @param m matrix to copy + */ + public GeneralMatrix(Matrix m) { + super(m); + } + + public Matrix duplicate() { + return new GeneralMatrix(this); + } + + /** Add a matrix to the instance. + * This method adds a matrix to the instance. It does modify the instance. + * @param m matrix to add + * @exception IllegalArgumentException if there is a dimension mismatch + */ + public void selfAdd(Matrix m) { + + // validity check + if ((rows != m.rows) || (columns != m.columns)) { + throw new IllegalArgumentException("cannot add a " + + m.rows + 'x' + m.columns + + " matrix to a " + + rows + 'x' + columns + + " matrix"); + } + + // addition loop + for (int index = 0; index < rows * columns; ++index) { + data[index] += m.data[index]; + } + + } + + /** Substract a matrix from the instance. + * This method substracts a matrix from the instance. It does modify the instance. + * @param m matrix to substract + * @exception IllegalArgumentException if there is a dimension mismatch + */ + public void selfSub(Matrix m) { + + // validity check + if ((rows != m.rows) || (columns != m.columns)) { + throw new IllegalArgumentException("cannot substract a " + + m.rows + 'x' + m.columns + + " matrix from a " + + rows + 'x' + columns + + " matrix"); + } + + // substraction loop + for (int index = 0; index < rows * columns; ++index) { + data[index] -= m.data[index]; + } + + } + + protected NonNullRange getRangeForRow(int i) { + return new NonNullRange(0, columns); + } + + protected NonNullRange getRangeForColumn(int j) { + return new NonNullRange(0, rows); + } + + private static final long serialVersionUID = 4350328622456299819L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/linalg/GeneralSquareMatrix.java b/src/mantissa/src/org/spaceroots/mantissa/linalg/GeneralSquareMatrix.java new file mode 100644 index 000000000..89797e53c --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/linalg/GeneralSquareMatrix.java @@ -0,0 +1,282 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.linalg; + +import java.io.Serializable; + +/** This class implements general square matrices of linear algebra. + + * @version $Id: GeneralSquareMatrix.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class GeneralSquareMatrix + extends SquareMatrix + implements Serializable, Cloneable { + + /** Simple constructor. + * This constructor builds a square matrix of specified order, all + * elements beeing zeros. + * @param order order of the matrix + */ + public GeneralSquareMatrix(int order) { + super(order); + permutations = null; + evenPermutations = true; + lower = null; + upper = null; + } + + /** Simple constructor. + * Build a matrix with specified elements. + * @param order order of the matrix + * @param data table of the matrix elements (stored row after row) + */ + public GeneralSquareMatrix(int order, double[] data) { + super(order, data); + permutations = null; + evenPermutations = true; + lower = null; + upper = null; + } + + /** Copy constructor. + * @param s square matrix to copy + */ + public GeneralSquareMatrix(GeneralSquareMatrix s) { + super(s); + + if (s.permutations != null) { + permutations = (int[]) s.permutations.clone(); + evenPermutations = s.evenPermutations; + lower = new LowerTriangularMatrix(s.lower); + upper = new UpperTriangularMatrix(s.upper); + } else { + permutations = null; + evenPermutations = true; + lower = null; + upper = null; + } + + } + + public Matrix duplicate() { + return new GeneralSquareMatrix(this); + } + + public void setElement(int i, int j, double value) { + super.setElement(i, j, value); + permutations = null; + evenPermutations = true; + lower = null; + upper = null; + } + + /** Add a matrix to the instance. + * This method adds a matrix to the instance. It does modify the instance. + * @param s square matrix to add + * @exception IllegalArgumentException if there is a dimension mismatch + */ + public void selfAdd(SquareMatrix s) { + + // validity check + if ((rows != s.rows) || (columns != s.columns)) { + throw new IllegalArgumentException("cannot add a " + + s.rows + 'x' + s.columns + + " matrix to a " + + rows + 'x' + columns + + " matrix"); + } + + // addition loop + for (int index = 0; index < rows * columns; ++index) { + data[index] += s.data[index]; + } + + } + + /** Substract a matrix from the instance. + * This method substracts a matrix from the instance. It does modify the instance. + * @param s square matrix to substract + * @exception IllegalArgumentException if there is a dimension mismatch + */ + public void selfSub(SquareMatrix s) { + + // validity check + if ((rows != s.rows) || (columns != s.columns)) { + throw new IllegalArgumentException("cannot substract a " + + s.rows + 'x' + s.columns + + " matrix from a " + + rows + 'x' + columns + + " matrix"); + } + + // substraction loop + for (int index = 0; index < rows * columns; ++index) { + data[index] -= s.data[index]; + } + + } + + public double getDeterminant(double epsilon) { + try { + if (permutations == null) + computeLUFactorization(epsilon); + double d = upper.getDeterminant(epsilon); + return evenPermutations ? d : -d; + } catch (SingularMatrixException e) { + return 0.0; + } + } + + public Matrix solve(Matrix b, double epsilon) + throws SingularMatrixException { + // validity check + if (b.getRows() != rows) { + throw new IllegalArgumentException("dimension mismatch"); + } + + if (permutations == null) { + computeLUFactorization(epsilon); + } + + // apply the permutations to the second member + double[] permData = new double[b.data.length]; + int bCols = b.getColumns(); + for (int i = 0; i < rows; ++i) { + NonNullRange range = b.getRangeForRow(permutations[i]); + for (int j = range.begin; j < range.end; ++j) { + permData[i * bCols + j] = b.data[permutations[i] * bCols + j]; + } + } + Matrix permB = MatrixFactory.buildMatrix(b.getRows(), bCols, permData); + + // solve the permuted system + return upper.solve(lower.solve(permB, epsilon), epsilon); + + } + + protected NonNullRange getRangeForRow(int i) { + return new NonNullRange(0, columns); + } + + protected NonNullRange getRangeForColumn(int j) { + return new NonNullRange(0, rows); + } + + private void computeLUFactorization(double epsilon) + throws SingularMatrixException { + // build a working copy of the matrix data + double[] work = new double[rows * columns]; + for (int index = 0; index < work.length; ++index) { + work[index] = data[index]; + } + + // initialize the permutations table to identity + permutations = new int[rows]; + for (int i = 0; i < rows; ++i) { + permutations[i] = i; + } + evenPermutations = true; + + for (int k = 0; k < rows; ++k) { + + // find the maximal element in the column + double maxElt = Math.abs(work[permutations[k] * columns + k]); + int jMax = k; + for (int i = k + 1; i < rows; ++i) { + double curElt = Math.abs(work[permutations[i] * columns + k]); + if (curElt > maxElt) { + maxElt = curElt; + jMax = i; + } + } + + if (maxElt < epsilon) { + throw new SingularMatrixException(); + } + + if (k != jMax) { + // do the permutation to have a large enough diagonal element + int tmp = permutations[k]; + permutations[k] = permutations[jMax]; + permutations[jMax] = tmp; + evenPermutations = ! evenPermutations; + } + + double inv = 1.0 / work[permutations[k] * columns + k]; + + // compute the contribution of the row to the triangular matrices + for (int i = k + 1; i < rows; ++i) { + double factor = inv * work[permutations[i] * columns + k]; + + // lower triangular matrix + work[permutations[i] * columns + k] = factor; + + // upper triangular matrix + int index1 = permutations[i] * columns + k; + int index2 = permutations[k] * columns + k; + for (int j = k + 1; j < columns; ++j) { + work[++index1] -= factor * work[++index2]; + } + } + } + + // build the matrices + double[] lowerData = new double[rows * columns]; + double[] upperData = new double[rows * columns]; + + int index = 0; + for (int i = 0; i < rows; ++i) { + int workIndex = permutations[i] * columns; + int j = 0; + + // lower part + while (j++ < i) { + lowerData[index] = work[workIndex++]; + upperData[index++] = 0.0; + } + + // diagonal + lowerData[index] = 1.0; + upperData[index++] = work[workIndex++]; + + // upper part + while (j++ < columns) { + lowerData[index] = 0.0; + upperData[index++] = work[workIndex++]; + } + } + + // release the memory as soon as possible + work = null; + + lower = new LowerTriangularMatrix(rows, lowerData); + upper = new UpperTriangularMatrix(rows, upperData); + + } + + private int[] permutations; + private boolean evenPermutations; + private LowerTriangularMatrix lower; + private UpperTriangularMatrix upper; + + private static final long serialVersionUID = -506293526695298279L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/linalg/LowerTriangularMatrix.java b/src/mantissa/src/org/spaceroots/mantissa/linalg/LowerTriangularMatrix.java new file mode 100644 index 000000000..062410779 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/linalg/LowerTriangularMatrix.java @@ -0,0 +1,219 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.linalg; + +import java.io.Serializable; + +/** This class implements lower triangular matrices of linear algebra. + + * @version $Id: LowerTriangularMatrix.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class LowerTriangularMatrix + extends SquareMatrix + implements Serializable, Cloneable { + + /** Simple constructor. + * This constructor builds a lower triangular matrix of specified order, all + * elements being zeros. + * @param order order of the matrix + */ + public LowerTriangularMatrix(int order) { + super(order); + } + + /** Simple constructor. + * Build a matrix with specified elements. + * @param order order of the matrix + * @param data table of the matrix elements (stored row after row) + */ + public LowerTriangularMatrix(int order, double[] data) { + super(order, data); + } + + /** Copy constructor. + * @param l lower triangular matrix to copy + */ + public LowerTriangularMatrix(LowerTriangularMatrix l) { + super(l); + } + + public Matrix duplicate() { + return new LowerTriangularMatrix(this); + } + + public void setElement(int i, int j, double value) { + if (i < j) { + throw new ArrayIndexOutOfBoundsException("cannot set elements" + + " above diagonal of a" + + " lower triangular matrix"); + } + super.setElement(i, j, value); + } + + /** Add a matrix to the instance. + * This method adds a matrix to the instance. It does modify the instance. + * @param l lower triangular matrix to add + * @exception IllegalArgumentException if there is a dimension mismatch + */ + public void selfAdd(LowerTriangularMatrix l) { + + // validity check + if ((rows != l.rows) || (columns != l.columns)) { + throw new IllegalArgumentException("cannot add a " + + l.rows + 'x' + l.columns + + " matrix to a " + + rows + 'x' + columns + + " matrix"); + } + + // addition loop + for (int i = 0; i < rows; ++i) { + for (int index = i * columns; index < i * (columns + 1) + 1; ++index) { + data[index] += l.data[index]; + } + } + + } + + /** Substract a matrix from the instance. + * This method substract a matrix from the instance. It does modify the instance. + * @param l lower triangular matrix to substract + * @exception IllegalArgumentException if there is a dimension mismatch + */ + public void selfSub(LowerTriangularMatrix l) { + + // validity check + if ((rows != l.rows) || (columns != l.columns)) { + throw new IllegalArgumentException("cannot substract a " + + l.rows + 'x' + l.columns + + " matrix from a " + + rows + 'x' + columns + + " matrix"); + } + + // substraction loop + for (int i = 0; i < rows; ++i) { + for (int index = i * columns; index < i * (columns + 1) + 1; ++index) { + data[index] -= l.data[index]; + } + } + + } + + public double getDeterminant(double epsilon) { + double determinant = data[0]; + for (int index = columns + 1; index < columns * columns; index += columns + 1) { + determinant *= data[index]; + } + return determinant; + } + + public Matrix solve(Matrix b, double epsilon) + throws SingularMatrixException { + // validity check + if (b.getRows () != rows) { + throw new IllegalArgumentException("dimension mismatch"); + } + + // prepare the data storage + int bRows = b.getRows(); + int bCols = b.getColumns(); + + double[] resultData = new double[bRows * bCols]; + int resultIndex = 0; + int lowerElements = 0; + int upperElements = 0; + int minJ = columns; + int maxJ = 0; + + // solve the linear system + for (int i = 0; i < rows; ++i) { + double diag = data[i * (columns + 1)]; + if (Math.abs(diag) < epsilon) { + throw new SingularMatrixException(); + } + double inv = 1.0 / diag; + + NonNullRange range = b.getRangeForRow(i); + minJ = Math.min(minJ, range.begin); + maxJ = Math.max(maxJ, range.end); + + int j = 0; + while (j < minJ) { + resultData[resultIndex] = 0.0; + ++resultIndex; + ++j; + } + + // compute the possibly non null elements + int bIndex = i * bCols + minJ; + while (j < maxJ) { + + // compute the current element + int index1 = i * columns; + int index2 = j; + double value = b.data[bIndex]; + while (index1 < i * (columns + 1)) { + value -= data[index1] * resultData[index2]; + ++index1; + index2 += bCols; + } + value *= inv; + resultData[resultIndex] = value; + + // count the affected upper and lower elements + // (in order to deduce the shape of the resulting matrix) + if (j < i) { + ++lowerElements; + } else if (i < j) { + ++upperElements; + } + + ++bIndex; + ++resultIndex; + ++j; + + } + + while (j < bCols) { + resultData[resultIndex] = 0.0; + ++resultIndex; + ++j; + } + + } + + return MatrixFactory.buildMatrix(bRows, bCols, resultData, + lowerElements, upperElements); + + } + + public NonNullRange getRangeForRow(int i) { + return new NonNullRange(0, i + 1); + } + + public NonNullRange getRangeForColumn(int j) { + return new NonNullRange(j, rows); + } + + private static final long serialVersionUID = 3592505328858227281L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/linalg/Matrix.java b/src/mantissa/src/org/spaceroots/mantissa/linalg/Matrix.java new file mode 100644 index 000000000..22cdf94bb --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/linalg/Matrix.java @@ -0,0 +1,484 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.linalg; + +import java.io.Serializable; + +/** This class factor all services common to matrices. + + *

    This class is the base class of all matrix implementations, it + * is also the base class of the {@link SquareMatrix} class which adds + * methods specific to square matrices.

    + + *

    This class both handles the storage of matrix elements and + * implements the classical operations on matrices (addition, + * substraction, multiplication, transposition). It relies on two + * protected methods ({@link #getRangeForRow} and {@link + * #getRangeForColumn}) to get tight loop bounds for matrices with + * known structures full of zeros. These methods should be + * implemented by derived classes to provide information about their + * specific shape to the general algorithms implemented by this + * abstract class.

    + + * @version $Id: Matrix.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public abstract class Matrix + implements Serializable { + /** Simple constructor. + * Build a matrix with null elements. + * @param rows number of rows of the matrix + * @param columns number of columns of the matrix + */ + protected Matrix(int rows, int columns) { + // sanity check + if (rows <= 0 || columns <= 0) { + throw new IllegalArgumentException("cannot build a matrix" + + " with negative or null dimension"); + } + + this.rows = rows; + this.columns = columns; + data = new double[rows * columns]; + for (int i = 0; i < data.length; ++i) { + data[i] = 0.0; + } + + } + + /** Simple constructor. + * Build a matrix with specified elements. + * @param rows number of rows of the matrix + * @param columns number of columns of the matrix + * @param data table of the matrix elements (stored row after row) + */ + public Matrix(int rows, int columns, double[] data) { + // sanity check + if (rows <= 0 || columns <= 0) { + throw new IllegalArgumentException("cannot build a matrix" + + " with negative or null dimension"); + } + + this.rows = rows; + this.columns = columns; + this.data = data; + + } + + /** Copy constructor. + * @param m matrix to copy + */ + protected Matrix(Matrix m) { + rows = m.rows; + columns = m.columns; + data = new double[rows * columns]; + System.arraycopy(m.data, 0, data, 0, m.data.length); + } + + /** Polymorphic copy operator. + * This method build a new object of the same type of the + * instance. It is somewhat similar to the {@link Object#clone} + * method, except that it has public access, it doesn't throw any + * specific exception and it returns a Matrix. + *@see Object#clone + */ + public abstract Matrix duplicate(); + + /** Get the number of rows of the matrix. + * @return number of rows + * @see #getColumns + */ + public int getRows() { + return rows; + } + + /** Get the number of columns of the matrix. + * @return number of columns + * @see #getRows + */ + public int getColumns() { + return columns; + } + + /** Get a matrix element. + * @param i row index, from 0 to rows - 1 + * @param j column index, from 0 to cols - 1 + * @return value of the element + * @exception ArrayIndexOutOfBoundsException if the indices are wrong + * @see #setElement + */ + public double getElement(int i, int j) { + if (i < 0 || i >= rows || j < 0 || j >= columns) { + throw new IllegalArgumentException("cannot get element (" + + i + ", " + j + ") from a " + + rows + 'x' + columns + + " matrix"); + } + return data[i * columns + j]; + } + + /** Set a matrix element. + * @param i row index, from 0 to rows - 1 + * @param j column index, from 0 to cols - 1 + * @param value value of the element + * @exception ArrayIndexOutOfBoundsException if the indices are wrong + * @see #getElement + */ + public void setElement(int i, int j, double value) { + if (i < 0 || i >= rows || j < 0 || j >= columns) { + throw new IllegalArgumentException("cannot set element (" + + i + ", " + j + ") in a " + + rows + 'x' + columns + + " matrix"); + } + data[i * columns + j] = value; + } + + /** Add a matrix to the instance. + * This method adds a matrix to the instance. It returns a new + * matrix and does not modify the instance. + * @param m matrix to add + * @return a new matrix containing the result + * @exception IllegalArgumentException if there is a dimension mismatch + */ + public Matrix add(Matrix m) { + + // validity check + if ((rows != m.rows) || (columns != m.columns)) { + throw new IllegalArgumentException("cannot add a " + + m.rows + 'x' + m.columns + + " matrix to a " + + rows + 'x' + columns + + " matrix"); + } + + double[] resultData = new double[rows * columns]; + int resultIndex = 0; + int lowerElements = 0; + int upperElements = 0; + + // external loop through the rows + for (int i = 0; i < rows; ++i) { + // compute the indices of the internal loop + NonNullRange r = NonNullRange.reunion(getRangeForRow(i), + m.getRangeForRow(i)); + + // assign the zeros before the non null range + int j = 0; + while (j < r.begin) { + resultData[resultIndex] = 0.0; + ++resultIndex; + ++j; + } + + // compute the possibly non null elements + while (j < r.end) { + + // compute the current element + resultData[resultIndex] = data[resultIndex] + m.data[resultIndex]; + + // count the affected upper and lower elements + // (in order to deduce the shape of the resulting matrix) + if (j < i) { + ++lowerElements; + } else if (i < j) { + ++upperElements; + } + + ++resultIndex; + ++j; + + } + + // assign the zeros after the non null range + while (j < columns) { + resultData[resultIndex++] = 0.0; + ++resultIndex; + ++j; + } + } + + return MatrixFactory.buildMatrix(rows, columns, resultData, + lowerElements, upperElements); + + } + + /** Substract a matrix from the instance. + * This method substracts a matrix from the instance. It returns a new + * matrix and does not modify the instance. + * @param m matrix to substract + * @return a new matrix containing the result + * @exception IllegalArgumentException if there is a dimension mismatch + */ + public Matrix sub(Matrix m) { + + // validity check + if ((rows != m.rows) || (columns != m.columns)) { + throw new IllegalArgumentException("cannot substract a " + + m.rows + 'x' + m.columns + + " matrix from a " + + rows + 'x' + columns + + " matrix"); + } + + double[] resultData = new double[rows * columns]; + int resultIndex = 0; + int lowerElements = 0; + int upperElements = 0; + + // external loop through the rows + for (int i = 0; i < rows; ++i) { + // compute the indices of the internal loop + NonNullRange r = NonNullRange.reunion(getRangeForRow(i), + m.getRangeForRow(i)); + + // assign the zeros before the non null range + int j = 0; + while (j < r.begin) { + resultData[resultIndex] = 0.0; + ++resultIndex; + ++j; + } + + // compute the possibly non null elements + while (j < r.end) { + + // compute the current element + resultData[resultIndex] = data[resultIndex] - m.data[resultIndex]; + + // count the affected upper and lower elements + // (in order to deduce the shape of the resulting matrix) + if (j < i) { + ++lowerElements; + } else if (i < j) { + ++upperElements; + } + + ++resultIndex; + ++j; + + } + + // assign the zeros after the non null range + while (j < columns) { + resultData[resultIndex++] = 0.0; + ++resultIndex; + ++j; + } + } + + return MatrixFactory.buildMatrix(rows, columns, resultData, + lowerElements, upperElements); + + } + + /** Multiply the instance by a matrix. + * This method multiplies the instance by a matrix. It returns a new + * matrix and does not modify the instance. + * @param m matrix by which to multiply + * @return a new matrix containing the result + * @exception IllegalArgumentException if there is a dimension mismatch + */ + public Matrix mul(Matrix m) { + + // validity check + if (columns != m.rows) { + throw new IllegalArgumentException("cannot multiply a " + + rows + 'x' + columns + + " matrix by a " + + m.rows + 'x' + m.columns + + " matrix"); + } + + double[] resultData = new double[rows * m.columns]; + int resultIndex = 0; + int lowerElements = 0; + int upperElements = 0; + + for (int i = 0; i < rows; ++i) { + for (int j = 0; j < m.columns; ++j) { + double value = 0.0; + + // compute the tighter possible indices of the internal loop + NonNullRange r = NonNullRange.intersection(getRangeForRow(i), + m.getRangeForColumn(j)); + + if (r.begin < r.end) { + int k = r.begin; + int idx = i * columns + k; + int midx = k * m.columns + j; + while (k++ < r.end) { + value += data[idx++] * m.data[midx]; + midx += m.columns; + } + + // count the affected upper and lower elements + // (in order to deduce the shape of the resulting matrix) + if (j < i) { + ++lowerElements; + } else if (i < j) { + ++upperElements; + } + + } + + // store the element value + resultData[resultIndex++] = value; + + } + } + + return MatrixFactory.buildMatrix(rows, m.columns, resultData, + lowerElements, upperElements); + + } + + /** Multiply the instance by a scalar. + * This method multiplies the instance by a scalar. It returns a new + * matrix and does not modify the instance. + * @param a scalar by which to multiply + * @return a new matrix containing the result + * @see #selfMul(double) + */ + public Matrix mul(double a) { + Matrix copy = duplicate(); + copy.selfMul(a); + return copy; + } + + /** Multiply the instance by a scalar. + * This method multiplies the instance by a scalar. + * It does modify the instance. + * @param a scalar by which to multiply + * @see #mul(double) + */ + public void selfMul(double a) { + for (int i = 0; i < rows; ++i) { + NonNullRange r = getRangeForRow(i); + for (int j = r.begin, index = i * columns + r.begin; j < r.end; ++j) { + data[index++] *= a; + } + } + + } + + /** Compute the transpose of the instance. + * This method transposes the instance. It returns a new + * matrix and does not modify the instance. + * @return a new matrix containing the result + */ + public Matrix getTranspose() { + + double[] resultData = new double[columns * rows]; + int resultIndex = 0; + int upperElements = 0; + int lowerElements = 0; + + for (int i = 0; i < columns; ++i) { + // compute the indices of the internal loop + NonNullRange range = getRangeForColumn(i); + + int j = 0; + int index = i; + + // assign the zeros before the non null range + while (j < range.begin) { + resultData[resultIndex++] = 0.0; + index += columns; + ++j; + } + + // compute the possibly non null elements + while (j < range.end) { + resultData[resultIndex] = data[index]; + + // count the affected upper and lower elements + // (in order to deduce the shape of the resulting matrix) + if (j < i) { + ++lowerElements; + } else if (i < j) { + ++upperElements; + } + + index += columns; + ++resultIndex; + ++j; + + } + + // assign the zeros after the non null range + while (j < rows) { + resultData[resultIndex] = 0.0; + ++resultIndex; + ++j; + } + + } + + return MatrixFactory.buildMatrix(columns, rows, resultData, + lowerElements, upperElements); + + } + + /** Set a range to the non null part covered by a row. + * @param i index of the row + * @return range of non nul elements in the specified row + * @see #getRangeForColumn + */ + protected abstract NonNullRange getRangeForRow(int i); + + /** Set a range to the non null part covered by a column. + * @param j index of the column + * @return range of non nul elements in the specified column + * @see #getRangeForRow + */ + protected abstract NonNullRange getRangeForColumn(int j); + + public String toString() { + String separator = System.getProperty("line.separator"); + + StringBuffer buf = new StringBuffer(); + for (int index = 0; index < rows * columns; ++index) { + if (index > 0) { + if (index % columns == 0) { + buf.append(separator); + } else { + buf.append(' '); + } + } + buf.append(Double.toString(data[index])); + } + + return buf.toString(); + + } + + /** number of rows of the matrix. */ + protected final int rows; + + /** number of columns of the matrix. */ + protected final int columns; + + /** array of the matrix elements. + * the elements are stored in a one dimensional array, row after row + */ + protected final double[] data; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/linalg/MatrixFactory.java b/src/mantissa/src/org/spaceroots/mantissa/linalg/MatrixFactory.java new file mode 100644 index 000000000..ba40ea02a --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/linalg/MatrixFactory.java @@ -0,0 +1,86 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.linalg; + +/** This class is a factory for the linear algebra package. + + *

    This class is devoted to building the right type of matrix + * according to the structure of the non null elements.

    + + *

    This is a utility class, no instance of this class should be + * built, so the constructor is explicitly made private.

    + + * @version $Id: MatrixFactory.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class MatrixFactory { + /** Simple constructor. + * Since the class is a utility class with only static methods, the + * constructor is made private to prevent creating instances of this + * class. + */ + private MatrixFactory() { + } + + /** Build a matrix of the right subtype. + * Build the right subtype of matrix according to the structure of + * the non null elements of the instance. Note that the information + * provided does not allow to build instances of the {@link + * SymetricalMatrix} class. When the data corresponding to a + * symetrical matrix is given, this method can only build an + * instance of the {@link GeneralSquareMatrix} class. + * @param rows number of row of the matrix + * @param columns number of columns of the matrix + * @param data table of the matrix elements (stored row after row) + * @param lowerElements number of non null elements in the lower triangle + * @param upperElements number of non null elements in the upper triangle + * @return a matrix containing the instance + */ + public static Matrix buildMatrix(int rows, int columns, double[] data, + int lowerElements, int upperElements) { + if (rows == columns) { + if (lowerElements == 0 && upperElements == 0) { + return new DiagonalMatrix(rows, data); + } else if (lowerElements == 0) { + return new UpperTriangularMatrix(rows, data); + } else if (upperElements == 0) { + return new LowerTriangularMatrix(rows, data); + } else { + return new GeneralSquareMatrix(rows, data); + } + } + return new GeneralMatrix(rows, columns, data); + } + + /** Build a matrix of the right subtype. + * Build the right subtype of matrix according to the dimensions. + * @param rows number of row of the matrix + * @param columns number of columns of the matrix + * @param data table of the matrix elements (stored row after row) + * @return a matrix containing the instance + */ + public static Matrix buildMatrix(int rows, int columns, double[] data) { + if (rows == columns) { + return new GeneralSquareMatrix(rows, data); + } + return new GeneralMatrix(rows, columns, data); + } + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/linalg/NonNullRange.java b/src/mantissa/src/org/spaceroots/mantissa/linalg/NonNullRange.java new file mode 100644 index 000000000..ded4ef3a0 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/linalg/NonNullRange.java @@ -0,0 +1,103 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.linalg; + +import java.io.Serializable; + +/** This class represents range of non null elements for rows or + * columns of matrices. + + *

    This class is used to reduce the computation loops by avoiding + * using elements that are known to be zeros. For full matrices, the + * range simply spans from 0 to the order of the matrix. For lower and + * upper triangular matrices, its width will depend on the index of + * the row or column considered. For diagonal matrices, the range is + * reduced to one index.

    + + *

    The indices provided by the class correspond to the elements + * that are non-null only according to the structure of + * the matrix. The real value of the element is not + * considered. Consider for example the following lower triangular + * matrix :

    + + *
    + *   1 0 0 0
    + *   2 8 0 0
    + *   0 5 3 0
    + *   3 2 4 4
    + * 
    + + *

    The third rows begins with zero, but this is not a consequence + * of the lower triangular structure, it is only a + * coincidence. Therefore, the range (in row/columns count) + * corresponding to third row will span from 0 to 2, not from 1 to 2.

    + + * @version $Id: NonNullRange.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ +class NonNullRange + implements Serializable, Cloneable { + + /** Index in row/column count of the first non-null element. */ + public final int begin; + + /** Index in row/column count after the last non-null element. */ + public final int end; + + /** Simple constructor. + * @param begin index in row/column count of the first non-null element + * @param end index in row/column count after the last non-null element + */ + public NonNullRange(int begin, int end) + { + this.begin = begin; + this.end = end; + } + + /** Copy constructor. + * @param range range to copy. + */ + public NonNullRange(NonNullRange range) { + begin = range.begin; + end = range.end; + } + + /** Build the intersection of two ranges. + * @param first first range to consider + * @param second second range to consider + */ + public static NonNullRange intersection(NonNullRange first, + NonNullRange second) { + return new NonNullRange(Math.max(first.begin, second.begin), + Math.min(first.end, second.end)); + } + + /** Build the reunion of two ranges. + * @param first first range to consider + * @param second second range to consider + */ + public static NonNullRange reunion(NonNullRange first, + NonNullRange second) { + return new NonNullRange(Math.min(first.begin, second.begin), + Math.max(first.end, second.end)); + } + + private static final long serialVersionUID = 8175301560126132666L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/linalg/SingularMatrixException.java b/src/mantissa/src/org/spaceroots/mantissa/linalg/SingularMatrixException.java new file mode 100644 index 000000000..302a9d2d7 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/linalg/SingularMatrixException.java @@ -0,0 +1,41 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.linalg; + +import org.spaceroots.mantissa.MantissaException; + +/** This class represent exceptions thrown by some matrix operations. + + * @version $Id: SingularMatrixException.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class SingularMatrixException + extends MantissaException { + + /** Simple constructor. + * Build an exception with a default message + */ + public SingularMatrixException() { + super("singular matrix"); + } + + private static final long serialVersionUID = 7531357987468317564L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/linalg/SquareMatrix.java b/src/mantissa/src/org/spaceroots/mantissa/linalg/SquareMatrix.java new file mode 100644 index 000000000..c89104a39 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/linalg/SquareMatrix.java @@ -0,0 +1,102 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.linalg; + +import java.io.Serializable; + +/** This class factor all services common to square matrices of linear algebra. + + *

    This class is the base class of all square matrix + * implementations. It extends the {@link Matrix} class with methods + * specific to square matrices.

    + + * @version $Id: SquareMatrix.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public abstract class SquareMatrix + extends Matrix + implements Serializable, Cloneable { + /** Simple constructor. + * Build a matrix with null elements. + * @param order order of the matrix + */ + protected SquareMatrix(int order) { + super(order, order); + } + + /** Simple constructor. + * Build a matrix with specified elements. + * @param order order of the matrix + * @param data table of the matrix elements (stored row after row) + */ + protected SquareMatrix(int order, double[] data) { + super(order, order, data); + } + + /** Copy constructor. + * @param m matrix to copy + */ + protected SquareMatrix(SquareMatrix m) { + super(m); + } + + /** Get the determinant of the matrix. + * @param epsilon threshold on matrix elements below which the + * matrix is considered singular (this is used by the derived + * classes that use a factorization to compute the determinant) + * @return the determinant of the matrix + */ + public abstract double getDeterminant(double epsilon); + + /** Invert the instance. + * @param epsilon threshold on matrix elements below which the + * matrix is considered singular + * @return the inverse matrix of the instance + * @exception SingularMatrixException if the matrix is singular + */ + public SquareMatrix getInverse(double epsilon) + throws SingularMatrixException { + return solve(new DiagonalMatrix (columns), epsilon); + } + + + /** Solve the A.X = B equation. + * @param b second term of the equation + * @param epsilon threshold on matrix elements below which the + * matrix is considered singular + * @return a matrix X such that A.X = B, where A is the instance + * @exception SingularMatrixException if the matrix is singular + */ + public abstract Matrix solve(Matrix b, double epsilon) + throws SingularMatrixException; + + /** Solve the A.X = B equation. + * @param b second term of the equation + * @param epsilon threshold on matrix elements below which the + * matrix is considered singular + * @return a matrix X such that A.X = B, where A is the instance + * @exception SingularMatrixException if the matrix is singular + */ + public SquareMatrix solve(SquareMatrix b, double epsilon) + throws SingularMatrixException { + return (SquareMatrix) solve((Matrix) b, epsilon); + } + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/linalg/SymetricalMatrix.java b/src/mantissa/src/org/spaceroots/mantissa/linalg/SymetricalMatrix.java new file mode 100644 index 000000000..6702e5b2f --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/linalg/SymetricalMatrix.java @@ -0,0 +1,226 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.linalg; + +import java.io.Serializable; + +/** This class implements symetrical matrices of linear algebra. + + * @version $Id: SymetricalMatrix.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class SymetricalMatrix + extends GeneralSquareMatrix + implements Serializable, Cloneable { + + /** Simple constructor. + * This constructor builds a symetrical matrix of specified order, all + * elements beeing zeros. + * @param order order of the matrix + */ + public SymetricalMatrix(int order) { + super(order); + } + + /** Simple constructor. + * Build a matrix with specified elements. + * @param order order of the matrix + * @param data table of the matrix elements (stored row after row) + */ + public SymetricalMatrix(int order, double[] data) { + super(order, data); + } + + /** Copy constructor. + * @param s square matrix to copy + */ + public SymetricalMatrix(SymetricalMatrix s) { + super(s); + } + + /** Build the symetrical matrix resulting from the product w.A.At. + * @param w multiplicative factor (weight) + * @param a base vector used to compute the symetrical contribution + */ + public SymetricalMatrix(double w, double[] a) { + super(a.length, new double[a.length * a.length]); + + for (int i = 0; i < a.length; ++i) { + int indexU = i * (columns + 1); + int indexL = indexU; + + double factor = w * a[i]; + data[indexU] = factor * a[i]; + + for (int j = i + 1; j < columns; ++j) { + ++indexU; + indexL += columns; + data[indexU] = factor * a[j]; + data[indexL] = data[indexU]; + } + } + + } + + public Matrix duplicate() { + return new SymetricalMatrix(this); + } + + /** Set a matrix element. + * On symetrical matrices, setting separately elements outside of + * the diagonal is forbidden, so this method throws an + * ArrayIndexOutOfBoundsException in this case. The {@link + * #setElementAndSymetricalElement} can be used to set both elements + * simultaneously. + * @param i row index, from 0 to rows - 1 + * @param j column index, from 0 to cols - 1 + * @param value value of the element + * @exception ArrayIndexOutOfBoundsException if the indices are wrong + * @see #setElementAndSymetricalElement + * @see Matrix#getElement + */ + public void setElement(int i, int j, double value) { + if (i != j) { + throw new ArrayIndexOutOfBoundsException("cannot separately set" + + " elements out of diagonal" + + " in a symetrical matrix"); + } + super.setElement(i, j, value); + } + + /** Set both a matrix element and its symetrical element. + * @param i row index of first element (column index of second + * element), from 0 to order - 1 + * @param j column index of first element (row index of second + * element), from 0 to order - 1 + * @param value value of the elements + * @exception ArrayIndexOutOfBoundsException if the indices are wrong + * @see #setElement + * @see Matrix#getElement + */ + public void setElementAndSymetricalElement(int i, int j, double value) { + super.setElement(i, j, value); + if (i != j) { + super.setElement(j, i, value); + } + } + + /** Add a matrix to the instance. + * This method adds a matrix to the instance. It does modify the instance. + * @param s symetrical matrix to add + * @exception IllegalArgumentException if there is a dimension mismatch + */ + public void selfAdd(SymetricalMatrix s) { + + // validity check + if ((rows != s.rows) || (columns != s.columns)) { + throw new IllegalArgumentException("cannot add a " + + s.rows + 'x' + s.columns + + " matrix to a " + + rows + 'x' + columns + + " matrix"); + } + + // addition loop + for (int i = 0; i < rows; ++i) { + int indexU = i * (columns + 1); + int indexL = indexU; + + data[indexU] += s.data[indexU]; + + for (int j = i + 1; j < columns; ++j) { + ++indexU; + indexL += columns; + data[indexU] += s.data[indexU]; + data[indexL] = data[indexU]; + } + } + + } + + /** Substract a matrix from the instance. + * This method substracts a matrix from the instance. It does modify the instance. + * @param s symetrical matrix to substract + * @exception IllegalArgumentException if there is a dimension mismatch + */ + public void selfSub(SymetricalMatrix s) { + + // validity check + if ((rows != s.rows) || (columns != s.columns)) { + throw new IllegalArgumentException("cannot substract a " + + s.rows + 'x' + s.columns + + " matrix from a " + + rows + 'x' + columns + + " matrix"); + } + + // substraction loop + for (int i = 0; i < rows; ++i) { + int indexU = i * (columns + 1); + int indexL = indexU; + + data[indexU] -= s.data[indexU]; + + for (int j = i + 1; j < columns; ++j) { + ++indexU; + indexL += columns; + data[indexU] -= s.data[indexU]; + data[indexL] = data[indexU]; + } + } + + } + + /** Add the symetrical matrix resulting from the product w.A.At to the instance. + * This method can be used to build progressively the matrices of + * least square problems. The instance is modified. + * @param w multiplicative factor (weight) + * @param a base vector used to compute the symetrical contribution + * @exception IllegalArgumentException if there is a dimension mismatch + */ + public void selfAddWAAt(double w, double[] a) { + if (rows != a.length) { + throw new IllegalArgumentException("cannot add a " + + a.length + 'x' + a.length + + " matrix to a " + + rows + 'x' + columns + + " matrix"); + } + + for (int i = 0; i < rows; ++i) { + int indexU = i * (columns + 1); + int indexL = indexU; + + double factor = w * a[i]; + data[indexU] += factor * a[i]; + + for (int j = i + 1; j < columns; ++j) { + ++indexU; + indexL += columns; + data[indexU] += factor * a[j]; + data[indexL] = data[indexU]; + } + } + + } + + private static final long serialVersionUID = -2083829252075519221L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/linalg/UpperTriangularMatrix.java b/src/mantissa/src/org/spaceroots/mantissa/linalg/UpperTriangularMatrix.java new file mode 100644 index 000000000..4c8bd417a --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/linalg/UpperTriangularMatrix.java @@ -0,0 +1,219 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.linalg; + +import java.io.Serializable; + +/** This class implements upper triangular matrices of linear algebra. + + * @version $Id: UpperTriangularMatrix.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class UpperTriangularMatrix + extends SquareMatrix + implements Serializable, Cloneable { + + /** Simple constructor. + * This constructor builds a upper triangular matrix of specified order, all + * elements being zeros. + * @param order order of the matrix + */ + public UpperTriangularMatrix(int order) { + super(order); + } + + /** Simple constructor. + * Build a matrix with specified elements. + * @param order order of the matrix + * @param data table of the matrix elements (stored row after row) + */ + public UpperTriangularMatrix(int order, double[] data) { + super(order, data); + } + + /** Copy constructor. + * @param u upper triangular matrix to copy + */ + public UpperTriangularMatrix(UpperTriangularMatrix u) { + super(u); + } + + public Matrix duplicate() { + return new UpperTriangularMatrix(this); + } + + public void setElement(int i, int j, double value) { + if (i > j) { + throw new ArrayIndexOutOfBoundsException("cannot set elements" + + " below diagonal of a" + + " upper triangular matrix"); + } + super.setElement(i, j, value); + } + + /** Add a matrix to the instance. + * This method adds a matrix to the instance. It does modify the instance. + * @param u upper triangular matrix to add + * @exception IllegalArgumentException if there is a dimension mismatch + */ + public void selfAdd(UpperTriangularMatrix u) { + + // validity check + if ((rows != u.rows) || (columns != u.columns)) { + throw new IllegalArgumentException("cannot add a " + + u.rows + 'x' + u.columns + + " matrix to a " + + rows + 'x' + columns + + " matrix"); + } + + // addition loop + for (int i = 0; i < rows; ++i) { + for (int index = i * (columns + 1); index < (i + 1) * columns; ++index) { + data[index] += u.data[index]; + } + } + + } + + /** Substract a matrix from the instance. + * This method substract a matrix from the instance. It does modify the instance. + * @param u upper triangular matrix to substract + * @exception IllegalArgumentException if there is a dimension mismatch + */ + public void selfSub(UpperTriangularMatrix u) { + + // validity check + if ((rows != u.rows) || (columns != u.columns)) { + throw new IllegalArgumentException("cannot substract a " + + u.rows + 'x' + u.columns + + " matrix from a " + + rows + 'x' + columns + + " matrix"); + } + + // substraction loop + for (int i = 0; i < rows; ++i) { + for (int index = i * (columns + 1); index < (i + 1) * columns; ++index) { + data[index] -= u.data[index]; + } + } + + } + + public double getDeterminant(double epsilon) { + double determinant = data[0]; + for (int index = columns + 1; index < columns * columns; index += columns + 1) { + determinant *= data[index]; + } + return determinant; + } + + public Matrix solve(Matrix b, double epsilon) + throws SingularMatrixException { + // validity check + if (b.getRows() != rows) { + throw new IllegalArgumentException("dimension mismatch"); + } + + // prepare the data storage + int bRows = b.getRows(); + int bCols = b.getColumns(); + + double[] resultData = new double[bRows * bCols]; + int resultIndex = bRows * bCols - 1; + int lowerElements = 0; + int upperElements = 0; + int minJ = columns; + int maxJ = 0; + + // solve the linear system + for (int i = rows - 1; i >= 0; --i) { + double diag = data[i * (columns + 1)]; + if (Math.abs(diag) < epsilon) { + throw new SingularMatrixException(); + } + double inv = 1.0 / diag; + + NonNullRange range = b.getRangeForRow(i); + minJ = Math.min(minJ, range.begin); + maxJ = Math.max(maxJ, range.end); + + int j = bCols - 1; + while (j >= maxJ) { + resultData[resultIndex] = 0.0; + --resultIndex; + --j; + } + + // compute the possibly non null elements + int bIndex = i * bCols + maxJ - 1; + while (j >= minJ) { + + // compute the current element + int index1 = (i + 1) * columns - 1; + int index2 = (bRows - 1) * bCols + j; + double value = b.data[bIndex]; + while (index1 >= i * (columns + 1)) { + value -= data[index1] * resultData[index2]; + --index1; + index2 -= bCols; + } + value *= inv; + resultData[resultIndex] = value; + + // count the affected upper and lower elements + // (in order to deduce the shape of the resulting matrix) + if (j < i) { + ++lowerElements; + } else if (i < j) { + ++upperElements; + } + + --bIndex; + --resultIndex; + --j; + + } + + while (j >= 0) { + resultData[resultIndex] = 0.0; + --resultIndex; + --j; + } + + } + + return MatrixFactory.buildMatrix(bRows, bCols, resultData, + lowerElements, upperElements); + + } + + public NonNullRange getRangeForRow(int i) { + return new NonNullRange (i, columns); + } + + public NonNullRange getRangeForColumn(int j) { + return new NonNullRange (0, j + 1); + } + + private static final long serialVersionUID = -197266611942032237L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/linalg/design/linalg.argo b/src/mantissa/src/org/spaceroots/mantissa/linalg/design/linalg.argo new file mode 100644 index 000000000..d86d04000 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/linalg/design/linalg.argo @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/mantissa/src/org/spaceroots/mantissa/linalg/design/linalg.xmi b/src/mantissa/src/org/spaceroots/mantissa/linalg/design/linalg.xmi new file mode 100644 index 000000000..d9da3faac --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/linalg/design/linalg.xmi @@ -0,0 +1,2252 @@ + + + + + + + + linear.algebra + + + + + + + + + + + + + + + + + + + + double + + + + + + + + + + Matrix + + + + + + + + + + + + + + + + + + + rows_ + + + + + + + + + + + columns_ + + + + + + + + + + + data_ + + + + + + + + + + + duplicate + + + + + + + + + + + + + + + + + + + + + + + + getRows + + + + + + + + + + + + + + + + + + + + + + i + + + + + + + + + + + + + getColumns + + + + + + + + + + + + + + + + + + + + + + j + + + + + + + + + + + + + getElement + + + + + + + + + + + + + + + + + + + + + + i + + + + + + + + + + + j + + + + + + + + + + + + + setElement + + + + + + + + + + + + + + + + + + + + + + i + + + + + + + + + + + j + + + + + + + + + + + value + + + + + + + + + + + + + add + + + + + + + + + + + + + + + + + + + + + + m + + + + + + + + + + + + + sub + + + + + + + + + + + + + + + + + + + + + + m + + + + + + + + + + + + + mul + + + + + + + + + + + + + + + + + + + + + + m + + + + + + + + + + + + + mul + + + + + + + + + + + + + + + + + + + + + + a + + + + + + + + + + + + + selfMul + + + + + + + + + + + + + + + + + + + + + + a + + + + + + + + + + + + + getTranspose + + + + + + + + + + + + + + + + + + + + + + + + getRangeForRow + + + + + + + + + + + + + + + + + + + + + + i + + + + + + + + + + + + + getRangeForColumn + + + + + + + + + + + + + + + + + + + + + + j + + + + + + + + + + + + + toString + + + + + + + + + + + + + + + + + + + + + + + + + + int + + + + + + + + + + int[] + + + + + + + + + + double[] + + + + + + + + + + UpperTriangularMatrix + + + + + + + + + + + + + + + + + + + + selfAdd + + + + + + + + + + + + + + + + + + + + + + u + + + + + + + + + + + + + selfSub + + + + + + + + + + + + + + + + + + + + + + u + + + + + + + + + + + + + + + NonNullRange + + + + + + + + + + + begin + + + + + + + + + + + end + + + + + + + + + + + + + void + + + + + + + + + + utility + + + + + + + + + + LowerTriangularMatrix + + + + + + + + + + + + + + + + + + + + selfAdd + + + + + + + + + + + + + + + + + + + + + + l + + + + + + + + + + + + + selfSub + + + + + + + + + + + + + + + + + + + + + + l + + + + + + + + + + + + + + + DiagonalMatrix + + + + + + + + + + + + + + + + + + + + GeneralSquareMatrix + + + + + + + + + + + + + + + + + + + + permutations_ + + + + + + + + + + + evenPermutations_ + + + + + + + + + + + selfAdd + + + + + + + + + + + + + + + + + + + + + + s + + + + + + + + + + + + + selfSub + + + + + + + + + + + + + + + + + + + + + + s + + + + + + + + + + + + + + + SymetricalMatrix + + + + + + + + + + + + + + selfAdd + + + + + + + + + + + + + + + + + + + + + + s + + + + + + + + + + + + + selfSub + + + + + + + + + + + + + + + + + + + + + + s + + + + + + + + + + + + + setElementAndSymetricalElement + + + + + + + + + + + + + + + + + + + + + + i + + + + + + + + + + + j + + + + + + + + + + + value + + + + + + + + + + + + + selfAddWAAt + + + + + + + + + + + + + + + + + + + + + + w + + + + + + + + + + + a + + + + + + + + + + + + + + + + + + + + + + + + reset + + + + + + + + + + + + + + + + + + + + + + + + + + GeneralMatrix + + + + + + + + + + + + + + + + + selfAdd + + + + + + + + + + + + + + + + + + + + + + m + + + + + + + + + + + + + selfSub + + + + + + + + + + + + + + + + + + + + + + m + + + + + + + + + + + + + + + + + + + + + + + + + + + SquareMatrix + + + + + + + + + + + + + + + + + + + + getDeterminant + + + + + + + + + + + + + + + + + + + + + + epsilon + + + + + + + + + + + + + getInverse + + + + + + + + + + + + + + + + + + + + + + epsilon + + + + + + + + + + + + + solve + + + + + + + + + + + + + + + + + + + + + + b + + + + + + + + + + + epsilon + + + + + + + + + + + + + solve + + + + + + + + + + + + + + + + + + + + + + b + + + + + + + + + + + epsilon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MatrixFactory + + + + + + + + + + + + + + + + + + + + + + + + buildMatrix + + + + + + + + + + + + + + + + + + + + + + rows + + + + + + + + + + + columns + + + + + + + + + + + data + + + + + + + + + + + lowerElements + + + + + + + + + + + upperElements + + + + + + + + + + + + + buildMatrix + + + + + + + + + + + + + + + + + + + + + + rows + + + + + + + + + + + columns + + + + + + + + + + + data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 1 + + + + + + + + + + + + + + + + + + + + 0 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + String + + + + + + + + + + + boolean + + + + + + + + + + + NonNullRange + + + + + + + + + + + + + + begin + + + + + + + + + + + end + + + + + + + + + + + intersection + + + + + + + + + + + + + + + + + + + + + + first + + + + + + + + + + + second + + + + + + + + + + + + + reunion + + + + + + + + + + + + + + + + + + + + + + first + + + + + + + + + + + second + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/mantissa/src/org/spaceroots/mantissa/linalg/design/linalg_classdiagram1.pgml b/src/mantissa/src/org/spaceroots/mantissa/linalg/design/linalg_classdiagram1.pgml new file mode 100644 index 000000000..39d9e3a74 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/linalg/design/linalg_classdiagram1.pgml @@ -0,0 +1,1046 @@ + + + + + + + + + Matrix + protected int rows_ +protected int columns_ +protected double[] data_ + public Matrix duplicate() +public int getRows(int i) +public int getColumns(int j) +public double getElement(int i, int j) +public void setElement(int i, int j, double value) +public Matrix add(Matrix m) +public Matrix sub(Matrix m) +public Matrix mul(Matrix m) +public Matrix mul(double a) +public void selfMul(double a) +public Matrix getTranspose() +public NonNullRange getRangeForRow(int i) +public NonNullRange getRangeForColumn(int j) +public String toString() + + + + + + + UpperTriangularMatrix + + public void selfAdd(UpperTriangularMatrix u) +public void selfSub(UpperTriangularMatrix u) + + + + + + + LowerTriangularMatrix + + public void selfAdd(LowerTriangularMatrix l) +public void selfSub(LowerTriangularMatrix l) + + + + + + + DiagonalMatrix + + + + + + + + + GeneralSquareMatrix + private int[] permutations_ +private boolean evenPermutations_ + public void selfAdd(SquareMatrix s) +public void selfSub(SquareMatrix s) + + + + + + + SymetricalMatrix + + public void selfAdd(SymetricalMatrix s) +public void selfSub(SymetricalMatrix s) +public void setElementAndSymetricalElement(int i, int j, double value) +public void selfAddWAAt(double w, double[] a) + + + + + + + GeneralMatrix + + public void selfAdd(Matrix m) +public void selfSub(Matrix m) + + + + + + + SquareMatrix + + public double getDeterminant(double epsilon) +public SquareMatrix getInverse(double epsilon) +public Matrix solve(Matrix b, double epsilon) +public SquareMatrix solve(SquareMatrix b, double epsilon) + + + + + + + MatrixFactory + + public Matrix buildMatrix(int rows, int columns, double[] data, int lowerElements, int upperElements) +public Matrix buildMatrix(int rows, int columns, double[] data) + + + + + + + NonNullRange + public int begin +public int end + public NonNullRange intersection(NonNullRange first, NonNullRange second) +public NonNullRange reunion(NonNullRange first, NonNullRange second) + + + + sourcePortFig="Fig6" + destPortFig="Fig0" + sourceFigNode="Fig6" + destFigNode="Fig0" + + + + + + + + + sourcePortFig="Fig7.0" + destPortFig="Fig0.0" + sourceFigNode="Fig7" + destFigNode="Fig0" + + + + + + + + + sourcePortFig="Fig4.0" + destPortFig="Fig7.0" + sourceFigNode="Fig4" + destFigNode="Fig7" + + + + + + + + + sourcePortFig="Fig1.0" + destPortFig="Fig7.0" + sourceFigNode="Fig1" + destFigNode="Fig7" + + + + + + + + + + + sourcePortFig="Fig2.0" + destPortFig="Fig7.0" + sourceFigNode="Fig2" + destFigNode="Fig7" + + + + + + + + + + + sourcePortFig="Fig3.0" + destPortFig="Fig7.0" + sourceFigNode="Fig3" + destFigNode="Fig7" + + + + + + + + + sourcePortFig="Fig0.0" + destPortFig="Fig8.0" + sourceFigNode="Fig0" + destFigNode="Fig8" + + + + + + + + + sourcePortFig="Fig8.0" + destPortFig="Fig4.0" + sourceFigNode="Fig8" + destFigNode="Fig4" + + + + + + + + + + + sourcePortFig="Fig8.0" + destPortFig="Fig1.0" + sourceFigNode="Fig8" + destFigNode="Fig1" + + + + + + + + + sourcePortFig="Fig8.0" + destPortFig="Fig2.0" + sourceFigNode="Fig8" + destFigNode="Fig2" + + + + + + + + + sourcePortFig="Fig8.0" + destPortFig="Fig3.0" + sourceFigNode="Fig8" + destFigNode="Fig3" + + + + + + + + + sourcePortFig="Fig8.0" + destPortFig="Fig6.0" + sourceFigNode="Fig8" + destFigNode="Fig6" + + + + + + + + + sourcePortFig="Fig4.0" + destPortFig="Fig2.0" + sourceFigNode="Fig4" + destFigNode="Fig2" + + + + + + + + + + sourcePortFig="Fig4.0" + destPortFig="Fig1.0" + sourceFigNode="Fig4" + destFigNode="Fig1" + + + + + + + + + + sourcePortFig="Fig1.0" + destPortFig="Fig8.0" + sourceFigNode="Fig1" + destFigNode="Fig8" + + + + + + + + + sourcePortFig="Fig2.0" + destPortFig="Fig8.0" + sourceFigNode="Fig2" + destFigNode="Fig8" + + + + + + + + + sourcePortFig="Fig3.0" + destPortFig="Fig8.0" + sourceFigNode="Fig3" + destFigNode="Fig8" + + + + + + + + + sourcePortFig="Fig5.0" + destPortFig="Fig4.0" + sourceFigNode="Fig5" + destFigNode="Fig4" + + + + + + + + + sourcePortFig="Fig0.0" + destPortFig="Fig9.0" + sourceFigNode="Fig0" + destFigNode="Fig9" + + + + + + + diff --git a/src/mantissa/src/org/spaceroots/mantissa/linalg/doc-files/org_spaceroots_mantissa_linalg_classes.png b/src/mantissa/src/org/spaceroots/mantissa/linalg/doc-files/org_spaceroots_mantissa_linalg_classes.png new file mode 100644 index 000000000..a158cbae7 Binary files /dev/null and b/src/mantissa/src/org/spaceroots/mantissa/linalg/doc-files/org_spaceroots_mantissa_linalg_classes.png differ diff --git a/src/mantissa/src/org/spaceroots/mantissa/linalg/package.html b/src/mantissa/src/org/spaceroots/mantissa/linalg/package.html new file mode 100644 index 000000000..5d669701c --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/linalg/package.html @@ -0,0 +1,21 @@ + + +This package provides classes to perform linear algebra computation. + +

    It is by no means a complete linear algebra system, it is +sufficient to solve least squares problems and linear equations +systems, but it lacks lots of functionalities and matrices types (for +example sparse matrices are not supported).

    + +

    The class diagram for the public classes of this package is +displayed below. The user will mainly use directly the implementation +classes rather than the abstract {@link +org.spaceroots.mantissa.linalg.Matrix} class. The shape of the matrices +used is often known in the algorithms so there is little need to hide +this shape behind the general class.

    + + + +@author L. Maisonobe + + diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/AbstractStepInterpolator.java b/src/mantissa/src/org/spaceroots/mantissa/ode/AbstractStepInterpolator.java new file mode 100644 index 000000000..ef5ee68ce --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/AbstractStepInterpolator.java @@ -0,0 +1,433 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.io.IOException; + +/** This abstract class represents an interpolator over the last step + * during an ODE integration. + * + *

    The various ODE integrators provide objects extending this class + * to the step handlers. The handlers can use these objects to + * retrieve the state vector at intermediate times between the + * previous and the current grid points (dense output).

    + * + * @see FirstOrderIntegrator + * @see SecondOrderIntegrator + * @see StepHandler + * + * @version $Id: AbstractStepInterpolator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + * + */ + +public abstract class AbstractStepInterpolator + implements StepInterpolator, Cloneable { + + /** previous time */ + protected double previousTime; + + /** current time */ + protected double currentTime; + + /** current time step */ + protected double h; + + /** current state */ + protected double[] currentState; + + /** interpolated time */ + protected double interpolatedTime; + + /** interpolated state */ + protected double[] interpolatedState; + + /** indicate if the step has been finalized or not. */ + private boolean finalized; + + /** integration direction. */ + private boolean forward; + + /** Simple constructor. + * This constructor builds an instance that is not usable yet, the + * {@link #reinitialize} method should be called before using the + * instance in order to initialize the internal arrays. This + * constructor is used only in order to delay the initialization in + * some cases. As an example, the {@link + * RungeKuttaFehlbergIntegrator} uses the prototyping design pattern + * to create the step interpolators by cloning an uninitialized + * model and latter initializing the copy. + */ + protected AbstractStepInterpolator() { + previousTime = Double.NaN; + currentTime = Double.NaN; + h = Double.NaN; + interpolatedTime = Double.NaN; + currentState = null; + interpolatedState = null; + finalized = false; + this.forward = true; + } + + /** Simple constructor. + * @param y reference to the integrator array holding the state at + * the end of the step + * @param forward integration direction indicator + */ + protected AbstractStepInterpolator(double[] y, boolean forward) { + + previousTime = Double.NaN; + currentTime = Double.NaN; + h = Double.NaN; + interpolatedTime = Double.NaN; + + currentState = y; + interpolatedState = new double[y.length]; + + finalized = false; + this.forward = forward; + + } + + /** Copy constructor. + + *

    The copied interpolator should have been finalized before the + * copy, otherwise the copy will not be able to perform correctly + * any derivative computation and will throw a {@link + * NullPointerException} later. Since we don't want this constructor + * to throw the exceptions finalization may involve and since we + * don't want this method to modify the state of the copied + * interpolator, finalization is not done + * automatically, it remains under user control.

    + + *

    The copy is a deep copy: its arrays are separated from the + * original arrays of the instance.

    + + * @param interpolator interpolator to copy from. + + */ + protected AbstractStepInterpolator(AbstractStepInterpolator interpolator) { + + previousTime = interpolator.previousTime; + currentTime = interpolator.currentTime; + h = interpolator.h; + interpolatedTime = interpolator.interpolatedTime; + + if (interpolator.currentState != null) { + int dimension = interpolator.currentState.length; + + currentState = new double[dimension]; + System.arraycopy(interpolator.currentState, 0, currentState, 0, + dimension); + + interpolatedState = new double[dimension]; + System.arraycopy(interpolator.interpolatedState, 0, interpolatedState, 0, + dimension); + } else { + currentState = null; + interpolatedState = null; + } + + finalized = interpolator.finalized; + forward = interpolator.forward; + + } + + /** Reinitialize the instance + * @param y reference to the integrator array holding the state at + * the end of the step + * @param forward integration direction indicator + */ + protected void reinitialize(double[] y, boolean forward) { + + previousTime = Double.NaN; + currentTime = Double.NaN; + h = Double.NaN; + interpolatedTime = Double.NaN; + + currentState = y; + interpolatedState = new double[y.length]; + + finalized = false; + this.forward = forward; + + } + + /** Copy the instance. + + *

    The copied interpolator should have been finalized before the + * copy, otherwise the copy will not be able to perform correctly any + * interpolation and will throw a {@link NullPointerException} + * later. Since we don't want this constructor to throw the + * exceptions finalization may involve and since we don't want this + * method to modify the state of the copied interpolator, + * finalization is not done automatically, it + * remains under user control.

    + + *

    The copy is a deep copy: its arrays are separated from the + * original arrays of the instance.

    + + *

    This method has been redeclared as public instead of protected.

    + + * @return a copy of the instance. + + */ + public abstract Object clone(); + + /** Shift one step forward. + * Copy the current time into the previous time, hence preparing the + * interpolator for future calls to {@link #storeTime storeTime} + */ + public void shift() { + previousTime = currentTime; + } + + /** Store the current step time. + * @param t current time + */ + public void storeTime(double t) { + + currentTime = t; + h = currentTime - previousTime; + interpolatedTime = t; + System.arraycopy(currentState, 0, interpolatedState, 0, + currentState.length); + + // the step is not finalized anymore + finalized = false; + + } + + /** + * Get the previous grid point time. + * @return previous grid point time + */ + public double getPreviousTime() { + return previousTime; + } + + /** + * Get the current grid point time. + * @return current grid point time + */ + public double getCurrentTime() { + return currentTime; + } + + /** + * Get the time of the interpolated point. + * If {@link #setInterpolatedTime} has not been called, it returns + * the current grid point time. + * @return interpolation point time + */ + public double getInterpolatedTime() { + return interpolatedTime; + } + + /** + * Set the time of the interpolated point. + *

    Setting the time outside of the current step is now allowed + * (it was not allowed up to version 5.4 of Mantissa), but should be + * used with care since the accuracy of the interpolator will + * probably be very poor far from this step. This allowance has been + * added to simplify implementation of search algorithms near the + * step endpoints.

    + * @param time time of the interpolated point + * @throws DerivativeException if this call induces an automatic + * step finalization that throws one + */ + public void setInterpolatedTime(double time) + throws DerivativeException { + interpolatedTime = time; + double oneMinusThetaH = currentTime - interpolatedTime; + computeInterpolatedState((h - oneMinusThetaH) / h, oneMinusThetaH); + } + + /** Check if the natural integration direction is forward. + *

    This method provides the integration direction as specified by the + * integrator itself, it avoid some nasty problems in degenerated + * cases like null steps due to cancellation at step initialization, + * step control or switching function triggering.

    + * @return true if the integration variable (time) increases during + * integration + */ + public boolean isForward() { + return forward; + } + + /** Compute the state at the interpolated time. + * This is the main processing method that should be implemented by + * the derived classes to perform the interpolation. + * @param theta normalized interpolation abscissa within the step + * (theta is zero at the previous time step and one at the current time step) + * @param oneMinusThetaH time gap between the interpolated time and + * the current time + * @throws DerivativeException this exception is propagated to the caller if the + * underlying user function triggers one + */ + protected abstract void computeInterpolatedState(double theta, + double oneMinusThetaH) + throws DerivativeException; + + /** + * Get the state vector of the interpolated point. + * @return state vector at time {@link #getInterpolatedTime} + */ + public double[] getInterpolatedState() { + return interpolatedState; + } + + + /** + * Finalize the step. + + *

    Some Runge-Kutta-Fehlberg integrators need fewer functions + * evaluations than their counterpart step interpolators. These + * interpolators should perform the last evaluations they need by + * themselves only if they need them. This method triggers these + * extra evaluations. It can be called directly by the user step + * handler and it is called automatically if {@link + * #setInterpolatedTime} is called.

    + + *

    Once this method has been called, no other + * evaluation will be performed on this step. If there is a need to + * have some side effects between the step handler and the + * differential equations (for example update some data in the + * equations once the step has been done), it is advised to call + * this method explicitly from the step handler before these side + * effects are set up. If the step handler induces no side effect, + * then this method can safely be ignored, it will be called + * transparently as needed.

    + + *

    Warning: since the step interpolator provided + * to the step handler as a parameter of the {@link + * StepHandler#handleStep handleStep} is valid only for the duration + * of the {@link StepHandler#handleStep handleStep} call, one cannot + * simply store a reference and reuse it later. One should first + * finalize the instance, then copy this finalized instance into a + * new object that can be kept.

    + + *

    This method calls the protected {@link #doFinalize doFinalize} + * method if it has never been called during this step and set a + * flag indicating that it has been called once. It is the {@link + * #doFinalize doFinalize} method which should perform the + * evaluations. This wrapping prevents from calling {@link + * #doFinalize doFinalize} several times and hence evaluating the + * differential equations too often. Therefore, subclasses are not + * allowed not reimplement it, they should rather reimplement {@link + * #doFinalize doFinalize}.

    + + * @throws DerivativeException this exception is propagated to the + * caller if the underlying user function triggers one + + */ + public final void finalizeStep() + throws DerivativeException { + if (! finalized) { + doFinalize(); + finalized = true; + } + } + + /** + * Really finalize the step. + * The default implementation of this method does nothing. + * @throws DerivativeException this exception is propagated to the + * caller if the underlying user function triggers one + */ + protected void doFinalize() + throws DerivativeException { + } + + public abstract void writeExternal(ObjectOutput out) + throws IOException; + + public abstract void readExternal(ObjectInput in) + throws IOException; + + /** Save the base state of the instance. + * This method performs step finalization if it has not been done + * before. + * @param out stream where to save the state + * @exception IOException in case of write error + */ + protected void writeBaseExternal(ObjectOutput out) + throws IOException { + + out.writeInt(currentState.length); + out.writeDouble(previousTime); + out.writeDouble(currentTime); + out.writeDouble(h); + out.writeBoolean(forward); + + for (int i = 0; i < currentState.length; ++i) { + out.writeDouble(currentState[i]); + } + + out.writeDouble(interpolatedTime); + + // we do not store the interpolated state, + // it will be recomputed as needed after reading + + // finalize the step (and don't bother saving the now true flag) + try { + finalizeStep(); + } catch (DerivativeException e) { + IOException ioe = new IOException(); + ioe.initCause(e); + throw ioe; + } + + } + + /** Read the base state of the instance. + * This method does neither set the interpolated + * time nor state. It is up to the derived class to reset it + * properly calling the {@link #setInterpolatedTime} method later, + * once all rest of the object state has been set up properly. + * @param in stream where to read the state from + * @return interpolated time be set later by the caller + * @exception IOException in case of read error + */ + protected double readBaseExternal(ObjectInput in) + throws IOException { + + int dimension = in.readInt(); + previousTime = in.readDouble(); + currentTime = in.readDouble(); + h = in.readDouble(); + forward = in.readBoolean(); + + currentState = new double[dimension]; + for (int i = 0; i < currentState.length; ++i) { + currentState[i] = in.readDouble(); + } + + // we do NOT handle the interpolated time and state here + interpolatedTime = Double.NaN; + interpolatedState = new double[dimension]; + + finalized = true; + + return in.readDouble(); + + } + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/AdaptiveStepsizeIntegrator.java b/src/mantissa/src/org/spaceroots/mantissa/ode/AdaptiveStepsizeIntegrator.java new file mode 100644 index 000000000..cbca7f979 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/AdaptiveStepsizeIntegrator.java @@ -0,0 +1,317 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +/** + * This abstract class holds the common part of all adaptive + * stepsize integrators for Ordinary Differential Equations. + + *

    These algorithms perform integration with stepsize control, which + * means the user does not specify the integration step but rather a + * tolerance on error. The error threshold is computed as + *

    + * threshold_i = absTol_i + relTol_i * max (abs (ym), abs (ym+1))
    + * 
    + * where absTol_i is the absolute tolerance for component i of the + * state vector and relTol_i is the relative tolerance for the same + * component. The user can also use only two scalar values absTol and + * relTol which will be used for all components.

    + + *

    If the estimated error for ym+1 is such that + *

    + * sqrt((sum (errEst_i / threshold_i)^2 ) / n) < 1
    + * 
    + + * (where n is the state vector dimension) then the step is accepted, + * otherwise the step is rejected and a new attempt is made with a new + * stepsize.

    + + * @version $Id: AdaptiveStepsizeIntegrator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public abstract class AdaptiveStepsizeIntegrator + implements FirstOrderIntegrator { + + /** Build an integrator with the given stepsize bounds. + * The default step handler does nothing. + * @param minStep minimal step (must be positive even for backward + * integration), the last step can be smaller than this + * @param maxStep maximal step (must be positive even for backward + * integration) + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + */ + public AdaptiveStepsizeIntegrator(double minStep, double maxStep, + double scalAbsoluteTolerance, + double scalRelativeTolerance) { + + this.minStep = minStep; + this.maxStep = maxStep; + this.initialStep = -1.0; + + this.scalAbsoluteTolerance = scalAbsoluteTolerance; + this.scalRelativeTolerance = scalRelativeTolerance; + this.vecAbsoluteTolerance = null; + this.vecRelativeTolerance = null; + + // set the default step handler + handler = DummyStepHandler.getInstance(); + + switchesHandler = new SwitchingFunctionsHandler(); + + } + + /** Build an integrator with the given stepsize bounds. + * The default step handler does nothing. + * @param minStep minimal step (must be positive even for backward + * integration), the last step can be smaller than this + * @param maxStep maximal step (must be positive even for backward + * integration) + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + */ + public AdaptiveStepsizeIntegrator(double minStep, double maxStep, + double[] vecAbsoluteTolerance, + double[] vecRelativeTolerance) { + + this.minStep = minStep; + this.maxStep = maxStep; + this.initialStep = -1.0; + + this.scalAbsoluteTolerance = 0; + this.scalRelativeTolerance = 0; + this.vecAbsoluteTolerance = vecAbsoluteTolerance; + this.vecRelativeTolerance = vecRelativeTolerance; + + // set the default step handler + handler = DummyStepHandler.getInstance(); + + switchesHandler = new SwitchingFunctionsHandler(); + + } + + /** Set the initial step size. + *

    This method allows the user to specify an initial positive + * step size instead of letting the integrator guess it by + * itself. If this method is not called before integration is + * started, the initial step size will be estimated by the + * integrator.

    + * @param initialStepSize initial step size to use (must be positive even + * for backward integration ; providing a negative value or a value + * outside of the min/max step interval will lead the integrator to + * ignore the value and compute the initial step size by itself) + */ + public void setInitialStepSize(double initialStepSize) { + if ((initialStepSize < minStep) || (initialStepSize > maxStep)) { + initialStep = -1.0; + } else { + initialStep = initialStepSize; + } + } + + /** Set the step handler for this integrator. + * The handler will be called by the integrator for each accepted + * step. + * @param handler handler for the accepted steps + */ + public void setStepHandler (StepHandler handler) { + this.handler = handler; + } + + /** Get the step handler for this integrator. + * @return the step handler for this integrator + */ + public StepHandler getStepHandler() { + return handler; + } + + /** Add a switching function to the integrator. + * @param function switching function + * @param maxCheckInterval maximal time interval between switching + * function checks (this interval prevents missing sign changes in + * case the integration steps becomes very large) + * @param convergence convergence threshold in the event time search + */ + public void addSwitchingFunction(SwitchingFunction function, + double maxCheckInterval, + double convergence) { + switchesHandler.add(function, maxCheckInterval, convergence); + } + + /** Initialize the integration step. + * @param equations differential equations set + * @param forward forward integration indicator + * @param order order of the method + * @param scale scaling vector for the state vector + * @param t0 start time + * @param y0 state vector at t0 + * @param yDot0 first time derivative of y0 + * @param y1 work array for a state vector + * @param yDot1 work array for the first time derivative of y1 + * @return first integration step + * @exception DerivativeException this exception is propagated to + * the caller if the underlying user function triggers one + */ + public double initializeStep(FirstOrderDifferentialEquations equations, + boolean forward, int order, double[] scale, + double t0, double[] y0, double[] yDot0, + double[] y1, double[] yDot1) + throws DerivativeException { + + if (initialStep > 0) { + // use the user provided value + return forward ? initialStep : -initialStep; + } + + // very rough first guess : h = 0.01 * ||y/scale|| / ||y'/scale|| + // this guess will be used to perform an Euler step + double ratio; + double yOnScale2 = 0; + double yDotOnScale2 = 0; + for (int j = 0; j < y0.length; ++j) { + ratio = y0[j] / scale[j]; + yOnScale2 += ratio * ratio; + ratio = yDot0[j] / scale[j]; + yDotOnScale2 += ratio * ratio; + } + + double h = ((yOnScale2 < 1.0e-10) || (yDotOnScale2 < 1.0e-10)) + ? 1.0e-6 : (0.01 * Math.sqrt(yOnScale2 / yDotOnScale2)); + if (! forward) { + h = -h; + } + + // perform an Euler step using the preceding rough guess + for (int j = 0; j < y0.length; ++j) { + y1[j] = y0[j] + h * yDot0[j]; + } + equations.computeDerivatives(t0 + h, y1, yDot1); + + // estimate the second derivative of the solution + double yDDotOnScale = 0; + for (int j = 0; j < y0.length; ++j) { + ratio = (yDot1[j] - yDot0[j]) / scale[j]; + yDDotOnScale += ratio * ratio; + } + yDDotOnScale = Math.sqrt(yDDotOnScale) / h; + + // step size is computed such that + // h^order * max (||y'/tol||, ||y''/tol||) = 0.01 + double maxInv2 = Math.max(Math.sqrt(yDotOnScale2), yDDotOnScale); + double h1 = (maxInv2 < 1.0e-15) + ? Math.max(1.0e-6, 0.001 * Math.abs(h)) + : Math.pow(0.01 / maxInv2, 1.0 / order); + h = Math.min(100.0 * Math.abs(h), h1); + h = Math.max(h, 1.0e-12 * Math.abs(t0)); // avoids cancellation when computing t1 - t0 + if (h < getMinStep()) { + h = getMinStep(); + } + if (h > getMaxStep()) { + h = getMaxStep(); + } + if (! forward) { + h = -h; + } + + return h; + + } + + /** Filter the integration step. + * @param h signed step + * @param acceptSmall if true, steps smaller than the minimal value + * are silently increased up to this value, if false such small + * steps generate an exception + * @return a bounded integration step (h if no bound is reach, or a bounded value) + * @exception IntegratorException if the step is too small and acceptSmall is false + */ + protected double filterStep(double h, boolean acceptSmall) + throws IntegratorException { + + if (Math.abs(h) < minStep) { + if (acceptSmall) { + h = (h < 0) ? -minStep : minStep; + } else { + throw new IntegratorException("minimal step size ({0}) reached," + + " integration needs {1}", + new String[] { + Double.toString(minStep), + Double.toString(Math.abs(h)) + }); + } + } + + if (h > maxStep) { + h = maxStep; + } else if (h < -maxStep) { + h = -maxStep; + } + + return h; + + } + + public abstract void integrate (FirstOrderDifferentialEquations equations, + double t0, double[] y0, + double t, double[] y) + throws DerivativeException, IntegratorException; + + /** Get the minimal step. + * @return minimal step + */ + public double getMinStep() { + return minStep; + } + + /** Get the maximal step. + * @return maximal step + */ + public double getMaxStep() { + return maxStep; + } + + /** Minimal step. */ + private double minStep; + + /** Maximal step. */ + private double maxStep; + + /** User supplied initial step. */ + private double initialStep; + + /** Allowed absolute scalar error. */ + protected double scalAbsoluteTolerance; + + /** Allowed relative scalar error. */ + protected double scalRelativeTolerance; + + /** Allowed absolute vectorial error. */ + protected double[] vecAbsoluteTolerance; + + /** Allowed relative vectorial error. */ + protected double[] vecRelativeTolerance; + + /** Step handler. */ + protected StepHandler handler; + + /** Switching functions handler. */ + protected SwitchingFunctionsHandler switchesHandler; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/ClassicalRungeKuttaIntegrator.java b/src/mantissa/src/org/spaceroots/mantissa/ode/ClassicalRungeKuttaIntegrator.java new file mode 100644 index 000000000..2eb1378e9 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/ClassicalRungeKuttaIntegrator.java @@ -0,0 +1,82 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +/** + * This class implements the classical fourth order Runge-Kutta + * integrator for Ordinary Differential Equations (it is the most + * often used Runge-Kutta method). + + *

    This method is an explicit Runge-Kutta method, its Butcher-array + * is the following one : + *

    + *    0  |  0    0    0    0
    + *   1/2 | 1/2   0    0    0
    + *   1/2 |  0   1/2   0    0
    + *    1  |  0    0    1    0
    + *       |--------------------
    + *       | 1/6  1/3  1/3  1/6
    + * 
    + *

    + + * @see EulerIntegrator + * @see GillIntegrator + * @see MidpointIntegrator + * @see ThreeEighthesIntegrator + + * @version $Id: ClassicalRungeKuttaIntegrator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class ClassicalRungeKuttaIntegrator + extends RungeKuttaIntegrator { + + private static final String methodName = new String("classical Runge-Kutta"); + + private static final double[] c = { + 1.0 / 2.0, 1.0 / 2.0, 1.0 + }; + + private static final double[][] a = { + { 1.0 / 2.0 }, + { 0.0, 1.0 / 2.0 }, + { 0.0, 0.0, 1.0 } + }; + + private static final double[] b = { + 1.0 / 6.0, 1.0 / 3.0, 1.0 / 3.0, 1.0 / 6.0 + }; + + /** Simple constructor. + * Build a fourth-order Runge-Kutta integrator with the given + * step. + * @param step integration step + */ + public ClassicalRungeKuttaIntegrator(double step) { + super(false, c, a, b, new ClassicalRungeKuttaStepInterpolator(), step); + } + + /** Get the name of the method. + * @return name of the method + */ + public String getName() { + return methodName; + } + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/ClassicalRungeKuttaStepInterpolator.java b/src/mantissa/src/org/spaceroots/mantissa/ode/ClassicalRungeKuttaStepInterpolator.java new file mode 100644 index 000000000..4c7c01fe7 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/ClassicalRungeKuttaStepInterpolator.java @@ -0,0 +1,113 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +/** + * This class implements a step interpolator for the classical fourth + * order Runge-Kutta integrator. + + *

    This interpolator allows to compute dense output inside the last + * step computed. The interpolation equation is consistent with the + * integration scheme : + + *

    + *   y(t_n + theta h) = y (t_n + h)
    + *                    + (1 - theta) (h/6) [ (-4 theta^2 + 5 theta - 1) y'_1
    + *                                          +(4 theta^2 - 2 theta - 2) (y'_2 + y'_3)
    + *                                          -(4 theta^2 +   theta + 1) y'_4
    + *                                        ]
    + * 
    + + * where theta belongs to [0 ; 1] and where y'_1 to y'_4 are the four + * evaluations of the derivatives already computed during the + * step.

    + + * @see ClassicalRungeKuttaIntegrator + + * @version $Id: ClassicalRungeKuttaStepInterpolator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +class ClassicalRungeKuttaStepInterpolator + extends RungeKuttaStepInterpolator { + + /** Simple constructor. + * This constructor builds an instance that is not usable yet, the + * {@link RungeKuttaStepInterpolator#reinitialize} method should be + * called before using the instance in order to initialize the + * internal arrays. This constructor is used only in order to delay + * the initialization in some cases. The {@link RungeKuttaIntegrator} + * class uses the prototyping design pattern to create the step + * interpolators by cloning an uninitialized model and latter initializing + * the copy. + */ + public ClassicalRungeKuttaStepInterpolator() { + } + + /** Copy constructor. + * @param interpolator interpolator to copy from. The copy is a deep + * copy: its arrays are separated from the original arrays of the + * instance + */ + public ClassicalRungeKuttaStepInterpolator(ClassicalRungeKuttaStepInterpolator interpolator) { + super(interpolator); + } + + /** + * Clone the instance. + * the copy is a deep copy: its arrays are separated from the + * original arrays of the instance + * @return a copy of the instance + */ + public Object clone() { + return new ClassicalRungeKuttaStepInterpolator(this); + } + + /** Compute the state at the interpolated time. + * This is the main processing method that should be implemented by + * the derived classes to perform the interpolation. + * @param theta normalized interpolation abscissa within the step + * (theta is zero at the previous time step and one at the current time step) + * @param oneMinusThetaH time gap between the interpolated time and + * the current time + * @throws DerivativeException this exception is propagated to the caller if the + * underlying user function triggers one + */ + protected void computeInterpolatedState(double theta, + double oneMinusThetaH) + throws DerivativeException { + + double fourTheta = 4 * theta; + double s = oneMinusThetaH / 6.0; + double coeff1 = s * ((-fourTheta + 5) * theta - 1); + double coeff23 = s * (( fourTheta - 2) * theta - 2); + double coeff4 = s * ((-fourTheta - 1) * theta - 1); + + for (int i = 0; i < interpolatedState.length; ++i) { + interpolatedState[i] = currentState[i] + + coeff1 * yDotK[0][i] + + coeff23 * (yDotK[1][i] + yDotK[2][i]) + + coeff4 * yDotK[3][i]; + } + + } + + private static final long serialVersionUID = -6576285612589783992L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/ContinuousOutputModel.java b/src/mantissa/src/org/spaceroots/mantissa/ode/ContinuousOutputModel.java new file mode 100644 index 000000000..75f242a93 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/ContinuousOutputModel.java @@ -0,0 +1,375 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +import java.util.ArrayList; +import java.util.Iterator; +import java.io.Serializable; + +/** + * This class stores all information provided by an ODE integrator + * during the integration process and build a continuous model of the + * solution from this. + + *

    This class act as a step handler from the integrator point of + * view. It is called iteratively during the integration process and + * stores a copy of all steps information in a sorted collection for + * later use. Once the integration process is over, the user can use + * the {@link #setInterpolatedTime setInterpolatedTime} and {@link + * #getInterpolatedState getInterpolatedState} to retrieve this + * information at any time. It is important to wait for the + * integration to be over before attempting to call {@link + * #setInterpolatedTime setInterpolatedTime} because some internal + * variables are set only once the last step has been handled.

    + + *

    This is useful for example if the main loop of the user + * application should remain independant from the integration process + * or if one needs to mimic the behaviour of an analytical model + * despite a numerical model is used (i.e. one needs the ability to + * get the model value at any time or to navigate through the + * data).

    + + *

    If problem modelization is done with several separate + * integration phases for contiguous intervals, the same + * ContinuousOutputModel can be used as step handler for all + * integration phases as long as they are performed in order and in + * the same direction. As an example, one can extrapolate the + * trajectory of a satellite with one model (i.e. one set of + * differential equations) up to the beginning of a maneuver, use + * another more complex model including thrusters modelization and + * accurate attitude control during the maneuver, and revert to the + * first model after the end of the maneuver. If the same continuous + * output model handles the steps of all integration phases, the user + * do not need to bother when the maneuver begins or ends, he has all + * the data available in a transparent manner.

    + + *

    An important feature of this class is that it implements the + * Serializable interface. This means that the result of + * an integration can be serialized and reused later (if stored into a + * persistent medium like a filesystem or a database) or elsewhere (if + * sent to another application). Only the result of the integration is + * stored, there is no reference to the integrated problem by + * itself.

    + + *

    One should be aware that the amount of data stored in a + * ContinuousOutputModel instance can be important if the state vector + * is large, if the integration interval is long or if the steps are + * small (which can result from small tolerance settings in {@link + * AdaptiveStepsizeIntegrator adaptive step size integrators}).

    + + * @see StepHandler + * @see StepInterpolator + + * @version $Id: ContinuousOutputModel.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class ContinuousOutputModel + implements StepHandler, Serializable { + + /** Simple constructor. + * Build an empty continuous output model. + */ + public ContinuousOutputModel() { + steps = new ArrayList(); + reset(); + } + + /** Append another model at the end of the instance. + * @param model model to add at the end of the instance + * @exception IllegalArgumentException if the model to append is not + * compatible with the instance (dimension of the state vector, + * propagation direction, hole between the dates) + */ + public void append(ContinuousOutputModel model) { + + if (model.steps.size() == 0) { + return; + } + + if (steps.size() == 0) { + initialTime = model.initialTime; + forward = model.forward; + } else { + + if (getInterpolatedState().length != model.getInterpolatedState().length) { + throw new IllegalArgumentException("state vector dimension mismatch"); + } + + if (forward ^ model.forward) { + throw new IllegalArgumentException("propagation direction mismatch"); + } + + StepInterpolator lastInterpolator = (StepInterpolator) steps.get(index); + double current = lastInterpolator.getCurrentTime(); + double previous = lastInterpolator.getPreviousTime(); + double step = current - previous; + double gap = model.getInitialTime() - current; + if (Math.abs(gap) > 1.0e-3 * Math.abs(step)) { + throw new IllegalArgumentException("hole between time ranges"); + } + + } + + for (Iterator iter = model.steps.iterator(); iter.hasNext(); ) { + AbstractStepInterpolator ai = (AbstractStepInterpolator) iter.next(); + steps.add(ai.clone()); + } + + index = steps.size() - 1; + finalTime = ((StepInterpolator) steps.get(index)).getCurrentTime(); + + } + + /** Determines whether this handler needs dense output. + *

    The essence of this class is to provide dense output over all + * steps, hence it requires the internal steps to provide themselves + * dense output. The method therefore returns always true.

    + * @return always true + */ + public boolean requiresDenseOutput() { + return true; + } + + /** Reset the step handler. + * Initialize the internal data as required before the first step is + * handled. + */ + public void reset() { + initialTime = Double.NaN; + finalTime = Double.NaN; + forward = true; + index = 0; + steps.clear(); + } + + /** Handle the last accepted step. + * A copy of the information provided by the last step is stored in + * the instance for later use. + * @param interpolator interpolator for the last accepted step. + * @param isLast true if the step is the last one + * @throws DerivativeException this exception is propagated to the + * caller if the underlying user function triggers one + */ + public void handleStep(StepInterpolator interpolator, boolean isLast) + throws DerivativeException { + + AbstractStepInterpolator ai = (AbstractStepInterpolator) interpolator; + + if (steps.size() == 0) { + initialTime = interpolator.getPreviousTime(); + forward = interpolator.isForward(); + } + + ai.finalizeStep(); + steps.add(ai.clone()); + + if (isLast) { + finalTime = ai.getCurrentTime(); + index = steps.size() - 1; + } + + } + + /** + * Get the initial integration time. + * @return initial integration time + */ + public double getInitialTime() { + return initialTime; + } + + /** + * Get the final integration time. + * @return final integration time + */ + public double getFinalTime() { + return finalTime; + } + + /** + * Get the time of the interpolated point. + * If {@link #setInterpolatedTime} has not been called, it returns + * the final integration time. + * @return interpolation point time + */ + public double getInterpolatedTime() { + return ((StepInterpolator) steps.get(index)).getInterpolatedTime(); + } + + /** Set the time of the interpolated point. + *

    This method should not be called before the + * integration is over because some internal variables are set only + * once the last step has been handled.

    + *

    Setting the time outside of the integration interval is now + * allowed (it was not allowed up to version 5.9 of Mantissa), but + * should be used with care since the accuracy of the interpolator + * will probably be very poor far from this interval. This allowance + * has been added to simplify implementation of search algorithms + * near the interval endpoints.

    + * @param time time of the interpolated point + */ + public void setInterpolatedTime(double time) { + + try { + // initialize the search with the complete steps table + int iMin = 0; + StepInterpolator sMin = (StepInterpolator) steps.get(iMin); + double tMin = 0.5 * (sMin.getPreviousTime() + sMin.getCurrentTime()); + + int iMax = steps.size() - 1; + StepInterpolator sMax = (StepInterpolator) steps.get(iMax); + double tMax = 0.5 * (sMax.getPreviousTime() + sMax.getCurrentTime()); + + // handle points outside of the integration interval + // or in the first and last step + if (locatePoint(time, sMin) <= 0) { + index = iMin; + sMin.setInterpolatedTime(time); + return; + } + if (locatePoint(time, sMax) >= 0) { + index = iMax; + sMax.setInterpolatedTime(time); + return; + } + + // reduction of the table slice size + while (iMax - iMin > 5) { + + // use the last estimated index as the splitting index + StepInterpolator si = (StepInterpolator) steps.get(index); + int location = locatePoint(time, si); + if (location < 0) { + iMax = index; + tMax = 0.5 * (si.getPreviousTime() + si.getCurrentTime()); + } else if (location > 0) { + iMin = index; + tMin = 0.5 * (si.getPreviousTime() + si.getCurrentTime()); + } else { + // we have found the target step, no need to continue searching + si.setInterpolatedTime(time); + return; + } + + // compute a new estimate of the index in the reduced table slice + int iMed = (iMin + iMax) / 2; + StepInterpolator sMed = (StepInterpolator) steps.get(iMed); + double tMed = 0.5 * (sMed.getPreviousTime() + sMed.getCurrentTime()); + + if ((Math.abs(tMed - tMin) < 1e-6) || (Math.abs(tMax - tMed) < 1e-6)) { + // too close to the bounds, we estimate using a simple dichotomy + index = iMed; + } else { + // estimate the index using a reverse quadratic polynom + // (reverse means we have i = P(t), thus allowing to simply + // compute index = P(time) rather than solving a quadratic equation) + double d12 = tMax - tMed; + double d23 = tMed - tMin; + double d13 = tMax - tMin; + double dt1 = time - tMax; + double dt2 = time - tMed; + double dt3 = time - tMin; + double iLagrange = ( (dt2 * dt3 * d23) * iMax + - (dt1 * dt3 * d13) * iMed + + (dt1 * dt2 * d12) * iMin) + / (d12 * d23 * d13); + index = (int) Math.rint(iLagrange); + } + + // force the next size reduction to be at least one tenth + int low = Math.max(iMin + 1, (9 * iMin + iMax) / 10); + int high = Math.min(iMax - 1, (iMin + 9 * iMax) / 10); + if (index < low) { + index = low; + } else if (index > high) { + index = high; + } + + } + + // now the table slice is very small, we perform an iterative search + index = iMin; + while ((index <= iMax) + && (locatePoint(time, (StepInterpolator) steps.get(index)) > 0)) { + ++index; + } + + StepInterpolator si = (StepInterpolator) steps.get(index); + + si.setInterpolatedTime(time); + + } catch (DerivativeException de) { + throw new RuntimeException("unexpected DerivativeException caught", de); + } + + } + + /** + * Get the state vector of the interpolated point. + * @return state vector at time {@link #getInterpolatedTime} + */ + public double[] getInterpolatedState() { + return ((StepInterpolator) steps.get(index)).getInterpolatedState(); + } + + /** Compare a step interval and a double. + * @param time point to locate + * @param interval step interval + * @return -1 if the double is before the interval, 0 if it is in + * the interval, and +1 if it is after the interval, according to + * the interval direction + */ + private int locatePoint(double time, StepInterpolator interval) { + if (forward) { + if (time < interval.getPreviousTime()) { + return -1; + } else if (time > interval.getCurrentTime()) { + return +1; + } else { + return 0; + } + } + if (time > interval.getPreviousTime()) { + return -1; + } else if (time < interval.getCurrentTime()) { + return +1; + } else { + return 0; + } + } + + /** Initial integration time. */ + private double initialTime; + + /** Final integration time. */ + private double finalTime; + + /** Integration direction indicator. */ + private boolean forward; + + /** Current interpolator index. */ + private int index; + + /** Steps table. */ + private ArrayList steps; + + private static final long serialVersionUID = 2259286184268533249L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/DerivativeException.java b/src/mantissa/src/org/spaceroots/mantissa/ode/DerivativeException.java new file mode 100644 index 000000000..aacaa115e --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/DerivativeException.java @@ -0,0 +1,50 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +import org.spaceroots.mantissa.MantissaException; + +/** + * This exception is made available to users to report + * the error conditions that are trigegred while computing + * the differential equations. + * @author Luc Maisonobe + * @version $Id: DerivativeException.java 1705 2006-09-17 19:57:39Z luc $ + */ +public class DerivativeException + extends MantissaException { + + /** Simple constructor. + * Build an exception by translating and formating a message + * @param specifier format specifier (to be translated) + * @param parts to insert in the format (no translation) + */ + public DerivativeException(String specifier, String[] parts) { + super(specifier, parts); + } + + /** Build an instance from an underlying cause. + * @param cause cause for the exception + */ + public DerivativeException(Throwable cause) { + super(cause); + } + + private static final long serialVersionUID = -4100440615830558122L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/DormandPrince54Integrator.java b/src/mantissa/src/org/spaceroots/mantissa/ode/DormandPrince54Integrator.java new file mode 100644 index 000000000..cb37fd557 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/DormandPrince54Integrator.java @@ -0,0 +1,153 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +/** + * This class implements the 5(4) Dormand-Prince integrator for Ordinary + * Differential Equations. + + *

    This integrator is an embedded Runge-Kutta-Fehlberg integrator + * of order 5(4) used in local extrapolation mode (i.e. the solution + * is computed using the high order formula) with stepsize control + * (and automatic step initialization) and continuous output. This + * method uses 7 functions evaluations per step. However, since this + * is an fsal, the last evaluation of one step is the same as + * the first evaluation of the next step and hence can be avoided. So + * the cost is really 6 functions evaluations per step.

    + + *

    This method has been published (whithout the continuous output + * that was added by Shampine in 1986) in the following article : + *

    + *  A family of embedded Runge-Kutta formulae
    + *  J. R. Dormand and P. J. Prince
    + *  Journal of Computational and Applied Mathematics
    + *  volume 6, no 1, 1980, pp. 19-26
    + * 

    + + * @version $Id: DormandPrince54Integrator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class DormandPrince54Integrator + extends RungeKuttaFehlbergIntegrator { + + private static final String methodName = new String("Dormand-Prince 5(4)"); + + private static final double[] c = { + 1.0/5.0, 3.0/10.0, 4.0/5.0, 8.0/9.0, 1.0, 1.0 + }; + + private static final double[][] a = { + {1.0/5.0}, + {3.0/40.0, 9.0/40.0}, + {44.0/45.0, -56.0/15.0, 32.0/9.0}, + {19372.0/6561.0, -25360.0/2187.0, 64448.0/6561.0, -212.0/729.0}, + {9017.0/3168.0, -355.0/33.0, 46732.0/5247.0, 49.0/176.0, -5103.0/18656.0}, + {35.0/384.0, 0.0, 500.0/1113.0, 125.0/192.0, -2187.0/6784.0, 11.0/84.0} + }; + + private static final double[] b = { + 35.0/384.0, 0.0, 500.0/1113.0, 125.0/192.0, -2187.0/6784.0, 11.0/84.0, 0.0 + }; + + private static final double e1 = 71.0 / 57600.0; + private static final double e3 = -71.0 / 16695.0; + private static final double e4 = 71.0 / 1920.0; + private static final double e5 = -17253.0 / 339200.0; + private static final double e6 = 22.0 / 525.0; + private static final double e7 = -1.0 / 40.0; + + /** Simple constructor. + * Build a fifth order Dormand-Prince integrator with the given step bounds + * @param minStep minimal step (must be positive even for backward + * integration), the last step can be smaller than this + * @param maxStep maximal step (must be positive even for backward + * integration) + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + */ + public DormandPrince54Integrator(double minStep, double maxStep, + double scalAbsoluteTolerance, + double scalRelativeTolerance) { + super(true, c, a, b, new DormandPrince54StepInterpolator(), + minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance); + } + + /** Simple constructor. + * Build a fifth order Dormand-Prince integrator with the given step bounds + * @param minStep minimal step (must be positive even for backward + * integration), the last step can be smaller than this + * @param maxStep maximal step (must be positive even for backward + * integration) + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + */ + public DormandPrince54Integrator(double minStep, double maxStep, + double[] vecAbsoluteTolerance, + double[] vecRelativeTolerance) { + super(true, c, a, b, new DormandPrince54StepInterpolator(), + minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance); + } + + /** Get the name of the method. + * @return name of the method + */ + public String getName() { + return methodName; + } + + /** Get the order of the method. + * @return order of the method + */ + public int getOrder() { + return 5; + } + + /** Compute the error ratio. + * @param yDotK derivatives computed during the first stages + * @param y0 estimate of the step at the start of the step + * @param y1 estimate of the step at the end of the step + * @param h current step + * @return error ratio, greater than 1 if step should be rejected + */ + protected double estimateError(double[][] yDotK, + double[] y0, double[] y1, + double h) { + + double error = 0; + + for (int j = 0; j < y0.length; ++j) { + double errSum = e1 * yDotK[0][j] + e3 * yDotK[2][j] + + e4 * yDotK[3][j] + e5 * yDotK[4][j] + + e6 * yDotK[5][j] + e7 * yDotK[6][j]; + + double yScale = Math.max(Math.abs(y0[j]), Math.abs(y1[j])); + double tol = (vecAbsoluteTolerance == null) + ? (scalAbsoluteTolerance + scalRelativeTolerance * yScale) + : (vecAbsoluteTolerance[j] + vecRelativeTolerance[j] * yScale); + double ratio = h * errSum / tol; + error += ratio * ratio; + + } + + return Math.sqrt(error / y0.length); + + } + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/DormandPrince54StepInterpolator.java b/src/mantissa/src/org/spaceroots/mantissa/ode/DormandPrince54StepInterpolator.java new file mode 100644 index 000000000..2ad4110d1 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/DormandPrince54StepInterpolator.java @@ -0,0 +1,205 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +/** + * This class represents an interpolator over the last step during an + * ODE integration for the 5(4) Dormand-Prince integrator. + * + * @see DormandPrince54Integrator + * + * @version $Id: DormandPrince54StepInterpolator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + * + */ + +class DormandPrince54StepInterpolator + extends RungeKuttaStepInterpolator { + + /** Simple constructor. + * This constructor builds an instance that is not usable yet, the + * {@link #reinitialize} method should be called before using the + * instance in order to initialize the internal arrays. This + * constructor is used only in order to delay the initialization in + * some cases. The {@link RungeKuttaFehlbergIntegrator} uses the + * prototyping design pattern to create the step interpolators by + * cloning an uninitialized model and latter initializing the copy. + */ + public DormandPrince54StepInterpolator() { + super(); + v1 = null; + v2 = null; + v3 = null; + v4 = null; + vectorsInitialized = false; + } + + /** Copy constructor. + * @param interpolator interpolator to copy from. The copy is a deep + * copy: its arrays are separated from the original arrays of the + * instance + */ + public DormandPrince54StepInterpolator(DormandPrince54StepInterpolator interpolator) { + + super(interpolator); + + if (interpolator.v1 == null) { + + v1 = null; + v2 = null; + v3 = null; + v4 = null; + vectorsInitialized = false; + + } else { + + int dimension = interpolator.v1.length; + + v1 = new double[dimension]; + v2 = new double[dimension]; + v3 = new double[dimension]; + v4 = new double[dimension]; + + System.arraycopy(interpolator.v1, 0, v1, 0, dimension); + System.arraycopy(interpolator.v2, 0, v2, 0, dimension); + System.arraycopy(interpolator.v3, 0, v3, 0, dimension); + System.arraycopy(interpolator.v4, 0, v4, 0, dimension); + + vectorsInitialized = interpolator.vectorsInitialized; + + } + + } + + /** + * Clone the instance. + * the copy is a deep copy: its arrays are separated from the + * original arrays of the instance + * @return a copy of the instance + */ + public Object clone() { + return new DormandPrince54StepInterpolator(this); + } + + /** Reinitialize the instance + * @param equations set of differential equations being integrated + * @param y reference to the integrator array holding the state at + * the end of the step + * @param yDotK reference to the integrator array holding all the + * intermediate slopes + * @param forward integration direction indicator + */ + public void reinitialize(FirstOrderDifferentialEquations equations, + double[] y, double[][] yDotK, boolean forward) { + super.reinitialize(equations, y, yDotK, forward); + v1 = null; + v2 = null; + v3 = null; + v4 = null; + vectorsInitialized = false; + } + + /** Store the current step time. + * @param t current time + */ + public void storeTime(double t) { + super.storeTime(t); + vectorsInitialized = false; + } + + /** Compute the state at the interpolated time. + * @param theta normalized interpolation abscissa within the step + * (theta is zero at the previous time step and one at the current time step) + * @param oneMinusThetaH time gap between the interpolated time and + * the current time + * @throws DerivativeException this exception is propagated to the caller if the + * underlying user function triggers one + */ + protected void computeInterpolatedState(double theta, + double oneMinusThetaH) + throws DerivativeException { + + if (! vectorsInitialized) { + + if (v1 == null) { + v1 = new double[interpolatedState.length]; + v2 = new double[interpolatedState.length]; + v3 = new double[interpolatedState.length]; + v4 = new double[interpolatedState.length]; + } + + // no step finalization is needed for this interpolator + + // we need to compute the interpolation vectors for this time step + for (int i = 0; i < interpolatedState.length; ++i) { + v1[i] = h * (a70 * yDotK[0][i] + a72 * yDotK[2][i] + a73 * yDotK[3][i] + + a74 * yDotK[4][i] + a75 * yDotK[5][i]); + v2[i] = h * yDotK[0][i] - v1[i]; + v3[i] = v1[i] - v2[i] - h * yDotK[6][i]; + v4[i] = h * (d0 * yDotK[0][i] + d2 * yDotK[2][i] + d3 * yDotK[3][i] + + d4 * yDotK[4][i] + d5 * yDotK[5][i] + d6 * yDotK[6][i]); + } + + vectorsInitialized = true; + + } + + // interpolate + double eta = oneMinusThetaH / h; + for (int i = 0; i < interpolatedState.length; ++i) { + interpolatedState[i] = currentState[i] + - eta * (v1[i] + - theta * (v2[i] + + theta * (v3[i] + + eta * v4[i]))); + } + + } + + /** First vector for interpolation. */ + private double[] v1; + + /** Second vector for interpolation. */ + private double[] v2; + + /** Third vector for interpolation. */ + private double[] v3; + + /** Fourth vector for interpolation. */ + private double[] v4; + + /** Initialization indicator for the interpolation vectors. */ + private boolean vectorsInitialized; + + // last row of the Butcher-array internal weights, note that a71 is null + private static final double a70 = 35.0 / 384.0; + private static final double a72 = 500.0 / 1113.0; + private static final double a73 = 125.0 / 192.0; + private static final double a74 = -2187.0 / 6784.0; + private static final double a75 = 11.0 / 84.0; + + // dense output of Shampine (1986), note that d1 is null + private static final double d0 = -12715105075.0 / 11282082432.0; + private static final double d2 = 87487479700.0 / 32700410799.0; + private static final double d3 = -10690763975.0 / 1880347072.0; + private static final double d4 = 701980252875.0 / 199316789632.0; + private static final double d5 = -1453857185.0 / 822651844.0; + private static final double d6 = 69997945.0 / 29380423.0; + + private static final long serialVersionUID = 4104157279605906956L; +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/DormandPrince853Integrator.java b/src/mantissa/src/org/spaceroots/mantissa/ode/DormandPrince853Integrator.java new file mode 100644 index 000000000..1eb8c85ae --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/DormandPrince853Integrator.java @@ -0,0 +1,258 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +/** + * This class implements the 8(5,3) Dormand-Prince integrator for Ordinary + * Differential Equations. + + *

    This integrator is an embedded Runge-Kutta-Fehlberg integrator + * of order 8(5,3) used in local extrapolation mode (i.e. the solution + * is computed using the high order formula) with stepsize control + * (and automatic step initialization) and continuous output. This + * method uses 12 functions evaluations per step for integration and 4 + * evaluations for interpolation. However, since the first + * interpolation evaluation is the same as the first integration + * evaluation of the next step, we have included it in the integrator + * rather than in the interpolator and specified the method was an + * fsal. Hence, despite we have 13 stages here, the cost is + * really 12 evaluations per step even if no interpolation is done, + * and the overcost of interpolation is only 3 evaluations.

    + + *

    This method is based on an 8(6) method by Dormand and Prince + * (i.e. order 8 for the integration and order 6 for error estimation) + * modified by Hairer and Wanner to use a 5th order error estimator + * with 3rd order correction. This modification was introduced because + * the original method failed in some cases (wrong steps can be + * accepted when step size is too large, for example in the + * Brusselator problem) and also had severe difficulties when + * applied to problems with discontinuities. This modification is + * explained in the second edition of the first volume (Nonstiff + * Problems) of the reference book by Hairer, Norsett and Wanner: + * Solving Ordinary Differential Equations (Springer-Verlag, + * ISBN 3-540-56670-8).

    + + * @version $Id: DormandPrince853Integrator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class DormandPrince853Integrator + extends RungeKuttaFehlbergIntegrator { + + private static final String methodName = new String("Dormand-Prince 8 (5, 3)"); + + private static final double sqrt6 = Math.sqrt(6.0); + + private static final double[] c = { + (12.0 - 2.0 * sqrt6) / 135.0, (6.0 - sqrt6) / 45.0, (6.0 - sqrt6) / 30.0, + (6.0 + sqrt6) / 30.0, 1.0/3.0, 1.0/4.0, 4.0/13.0, 127.0/195.0, 3.0/5.0, + 6.0/7.0, 1.0, 1.0 + }; + + private static final double[][] a = { + + // k2 + {(12.0 - 2.0 * sqrt6) / 135.0}, + + // k3 + {(6.0 - sqrt6) / 180.0, (6.0 - sqrt6) / 60.0}, + + // k4 + {(6.0 - sqrt6) / 120.0, 0.0, (6.0 - sqrt6) / 40.0}, + + // k5 + {(462.0 + 107.0 * sqrt6) / 3000.0, 0.0, + (-402.0 - 197.0 * sqrt6) / 1000.0, (168.0 + 73.0 * sqrt6) / 375.0}, + + // k6 + {1.0 / 27.0, 0.0, 0.0, (16.0 + sqrt6) / 108.0, (16.0 - sqrt6) / 108.0}, + + // k7 + {19.0 / 512.0, 0.0, 0.0, (118.0 + 23.0 * sqrt6) / 1024.0, + (118.0 - 23.0 * sqrt6) / 1024.0, -9.0 / 512.0}, + + // k8 + {13772.0 / 371293.0, 0.0, 0.0, (51544.0 + 4784.0 * sqrt6) / 371293.0, + (51544.0 - 4784.0 * sqrt6) / 371293.0, -5688.0 / 371293.0, 3072.0 / 371293.0}, + + // k9 + {58656157643.0 / 93983540625.0, 0.0, 0.0, + (-1324889724104.0 - 318801444819.0 * sqrt6) / 626556937500.0, + (-1324889724104.0 + 318801444819.0 * sqrt6) / 626556937500.0, + 96044563816.0 / 3480871875.0, 5682451879168.0 / 281950621875.0, + -165125654.0 / 3796875.0}, + + // k10 + {8909899.0 / 18653125.0, 0.0, 0.0, + (-4521408.0 - 1137963.0 * sqrt6) / 2937500.0, + (-4521408.0 + 1137963.0 * sqrt6) / 2937500.0, + 96663078.0 / 4553125.0, 2107245056.0 / 137915625.0, + -4913652016.0 / 147609375.0, -78894270.0 / 3880452869.0}, + + // k11 + {-20401265806.0 / 21769653311.0, 0.0, 0.0, + (354216.0 + 94326.0 * sqrt6) / 112847.0, + (354216.0 - 94326.0 * sqrt6) / 112847.0, + -43306765128.0 / 5313852383.0, -20866708358144.0 / 1126708119789.0, + 14886003438020.0 / 654632330667.0, 35290686222309375.0 / 14152473387134411.0, + -1477884375.0 / 485066827.0}, + + // k12 + {39815761.0 / 17514443.0, 0.0, 0.0, + (-3457480.0 - 960905.0 * sqrt6) / 551636.0, + (-3457480.0 + 960905.0 * sqrt6) / 551636.0, + -844554132.0 / 47026969.0, 8444996352.0 / 302158619.0, + -2509602342.0 / 877790785.0, -28388795297996250.0 / 3199510091356783.0, + 226716250.0 / 18341897.0, 1371316744.0 / 2131383595.0}, + + // k13 should be for interpolation only, but since it is the same + // stage as the first evaluation of the next step, we perform it + // here at no cost by specifying this is an fsal method + {104257.0/1920240.0, 0.0, 0.0, 0.0, 0.0, 3399327.0/763840.0, + 66578432.0/35198415.0, -1674902723.0/288716400.0, + 54980371265625.0/176692375811392.0, -734375.0/4826304.0, + 171414593.0/851261400.0, 137909.0/3084480.0} + + }; + + private static final double[] b = { + 104257.0/1920240.0, + 0.0, + 0.0, + 0.0, + 0.0, + 3399327.0/763840.0, + 66578432.0/35198415.0, + -1674902723.0/288716400.0, + 54980371265625.0/176692375811392.0, + -734375.0/4826304.0, + 171414593.0/851261400.0, + 137909.0/3084480.0, + 0.0 + }; + + private static final double e1_01 = 116092271.0 / 8848465920.0; + private static final double e1_06 = -1871647.0 / 1527680.0; + private static final double e1_07 = -69799717.0 / 140793660.0; + private static final double e1_08 = 1230164450203.0 / 739113984000.0; + private static final double e1_09 = -1980813971228885.0 / 5654156025964544.0; + private static final double e1_10 = 464500805.0 / 1389975552.0; + private static final double e1_11 = 1606764981773.0 / 19613062656000.0; + private static final double e1_12 = -137909.0 / 6168960.0; + + private static final double e2_01 = -364463.0 / 1920240.0; + private static final double e2_06 = 3399327.0 / 763840.0; + private static final double e2_07 = 66578432.0 / 35198415.0; + private static final double e2_08 = -1674902723.0 / 288716400.0; + private static final double e2_09 = -74684743568175.0 / 176692375811392.0; + private static final double e2_10 = -734375.0 / 4826304.0; + private static final double e2_11 = 171414593.0 / 851261400.0; + private static final double e2_12 = 69869.0 / 3084480.0; + + /** Simple constructor. + * Build an eighth order Dormand-Prince integrator with the given step bounds + * @param minStep minimal step (must be positive even for backward + * integration), the last step can be smaller than this + * @param maxStep maximal step (must be positive even for backward + * integration) + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + */ + public DormandPrince853Integrator(double minStep, double maxStep, + double scalAbsoluteTolerance, + double scalRelativeTolerance) { + super(true, c, a, b, + new DormandPrince853StepInterpolator(), + minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance); + } + + /** Simple constructor. + * Build an eighth order Dormand-Prince integrator with the given step bounds + * @param minStep minimal step (must be positive even for backward + * integration), the last step can be smaller than this + * @param maxStep maximal step (must be positive even for backward + * integration) + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + */ + public DormandPrince853Integrator(double minStep, double maxStep, + double[] vecAbsoluteTolerance, + double[] vecRelativeTolerance) { + super(true, c, a, b, + new DormandPrince853StepInterpolator(), + minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance); + } + + /** Get the name of the method. + * @return name of the method + */ + public String getName() { + return methodName; + } + + /** Get the order of the method. + * @return order of the method + */ + public int getOrder() { + return 8; + } + + /** Compute the error ratio. + * @param yDotK derivatives computed during the first stages + * @param y0 estimate of the step at the start of the step + * @param y1 estimate of the step at the end of the step + * @param h current step + * @return error ratio, greater than 1 if step should be rejected + */ + protected double estimateError(double[][] yDotK, + double[] y0, double[] y1, + double h) { + double error1 = 0; + double error2 = 0; + + for (int j = 0; j < y0.length; ++j) { + double errSum1 = e1_01 * yDotK[0][j] + e1_06 * yDotK[5][j] + + e1_07 * yDotK[6][j] + e1_08 * yDotK[7][j] + + e1_09 * yDotK[8][j] + e1_10 * yDotK[9][j] + + e1_11 * yDotK[10][j] + e1_12 * yDotK[11][j]; + double errSum2 = e2_01 * yDotK[0][j] + e2_06 * yDotK[5][j] + + e2_07 * yDotK[6][j] + e2_08 * yDotK[7][j] + + e2_09 * yDotK[8][j] + e2_10 * yDotK[9][j] + + e2_11 * yDotK[10][j] + e2_12 * yDotK[11][j]; + + double yScale = Math.max(Math.abs(y0[j]), Math.abs(y1[j])); + double tol = (vecAbsoluteTolerance == null) + ? (scalAbsoluteTolerance + scalRelativeTolerance * yScale) + : (vecAbsoluteTolerance[j] + vecRelativeTolerance[j] * yScale); + double ratio1 = errSum1 / tol; + error1 += ratio1 * ratio1; + double ratio2 = errSum2 / tol; + error2 += ratio2 * ratio2; + } + + double den = error1 + 0.01 * error2; + if (den <= 0.0) { + den = 1.0; + } + + return Math.abs(h) * error1 / Math.sqrt(y0.length * den); + + } + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/DormandPrince853StepInterpolator.java b/src/mantissa/src/org/spaceroots/mantissa/ode/DormandPrince853StepInterpolator.java new file mode 100644 index 000000000..5bb3fd930 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/DormandPrince853StepInterpolator.java @@ -0,0 +1,415 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +import java.io.ObjectOutput; +import java.io.ObjectInput; +import java.io.IOException; + +/** + * This class represents an interpolator over the last step during an + * ODE integration for the 8(5,3) Dormand-Prince integrator. + * + * @see DormandPrince853Integrator + * + * @version $Id: DormandPrince853StepInterpolator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + * + */ + +class DormandPrince853StepInterpolator + extends RungeKuttaStepInterpolator { + + /** Simple constructor. + * This constructor builds an instance that is not usable yet, the + * {@link #reinitialize} method should be called before using the + * instance in order to initialize the internal arrays. This + * constructor is used only in order to delay the initialization in + * some cases. The {@link RungeKuttaFehlbergIntegrator} uses the + * prototyping design pattern to create the step interpolators by + * cloning an uninitialized model and latter initializing the copy. + */ + public DormandPrince853StepInterpolator() { + super(); + yDotKLast = null; + yTmp = null; + v = null; + vectorsInitialized = false; + } + + /** Copy constructor. + * @param interpolator interpolator to copy from. The copy is a deep + * copy: its arrays are separated from the original arrays of the + * instance + */ + public DormandPrince853StepInterpolator(DormandPrince853StepInterpolator interpolator) { + + super(interpolator); + + if (interpolator.currentState == null) { + + yDotKLast = null; + v = null; + vectorsInitialized = false; + + } else { + + int dimension = interpolator.currentState.length; + + yDotKLast = new double[3][]; + for (int k = 0; k < yDotKLast.length; ++k) { + yDotKLast[k] = new double[dimension]; + System.arraycopy(interpolator.yDotKLast[k], 0, yDotKLast[k], 0, + dimension); + } + + v = new double[7][]; + for (int k = 0; k < v.length; ++k) { + v[k] = new double[dimension]; + System.arraycopy(interpolator.v[k], 0, v[k], 0, dimension); + } + + vectorsInitialized = interpolator.vectorsInitialized; + + } + + // the step has been finalized, we don't need this anymore + yTmp = null; + + } + + /** + * Clone the instance. + * the copy is a deep copy: its arrays are separated from the + * original arrays of the instance + * @return a copy of the instance + */ + public Object clone() { + return new DormandPrince853StepInterpolator(this); + } + + /** Reinitialize the instance + * Some Runge-Kutta-Fehlberg integrators need fewer functions + * evaluations than their counterpart step interpolators. So the + * interpolator should perform the last evaluations they need by + * themselves. The {@link RungeKuttaFehlbergIntegrator + * RungeKuttaFehlbergIntegrator} abstract class calls this method in + * order to let the step interpolator perform the evaluations it + * needs. These evaluations will be performed during the call to + * doFinalize if any, i.e. only if the step handler + * either calls the {@link AbstractStepInterpolator#finalizeStep + * finalizeStep} method or the {@link + * AbstractStepInterpolator#getInterpolatedState getInterpolatedState} + * method (for an interpolator which needs a finalization) or if it clones + * the step interpolator. + * @param equations set of differential equations being integrated + * @param y reference to the integrator array holding the state at + * the end of the step + * @param yDotK reference to the integrator array holding all the + * intermediate slopes + * @param forward integration direction indicator + */ + public void reinitialize(FirstOrderDifferentialEquations equations, + double[] y, double[][] yDotK, boolean forward) { + + super.reinitialize(equations, y, yDotK, forward); + + int dimension = currentState.length; + + yDotKLast = new double[3][]; + for (int k = 0; k < yDotKLast.length; ++k) { + yDotKLast[k] = new double[dimension]; + } + + yTmp = new double[dimension]; + + v = new double[7][]; + for (int k = 0; k < v.length; ++k) { + v[k] = new double[dimension]; + } + + vectorsInitialized = false; + + } + + /** Store the current step time. + * @param t current time + */ + public void storeTime(double t) { + super.storeTime(t); + vectorsInitialized = false; + } + + /** Compute the state at the interpolated time. + * This is the main processing method that should be implemented by + * the derived classes to perform the interpolation. + * @param theta normalized interpolation abscissa within the step + * (theta is zero at the previous time step and one at the current time step) + * @param oneMinusThetaH time gap between the interpolated time and + * the current time + * @throws DerivativeException this exception is propagated to the caller if the + * underlying user function triggers one + */ + protected void computeInterpolatedState(double theta, + double oneMinusThetaH) + throws DerivativeException { + + if (! vectorsInitialized) { + + if (v == null) { + v = new double[7][]; + for (int k = 0; k < 7; ++k) { + v[k] = new double[interpolatedState.length]; + } + } + + // perform the last evaluations if they have not been done yet + finalizeStep(); + + // compute the interpolation vectors for this time step + for (int i = 0; i < interpolatedState.length; ++i) { + v[0][i] = h * (b_01 * yDotK[0][i] + b_06 * yDotK[5][i] + b_07 * yDotK[6][i] + + b_08 * yDotK[7][i] + b_09 * yDotK[8][i] + b_10 * yDotK[9][i] + + b_11 * yDotK[10][i] + b_12 * yDotK[11][i]); + v[1][i] = h * yDotK[0][i] - v[0][i]; + v[2][i] = v[0][i] - v[1][i] - h * yDotK[12][i]; + for (int k = 0; k < d.length; ++k) { + v[k+3][i] = h * (d[k][0] * yDotK[0][i] + d[k][1] * yDotK[5][i] + d[k][2] * yDotK[6][i] + + d[k][3] * yDotK[7][i] + d[k][4] * yDotK[8][i] + d[k][5] * yDotK[9][i] + + d[k][6] * yDotK[10][i] + d[k][7] * yDotK[11][i] + d[k][8] * yDotK[12][i] + + d[k][9] * yDotKLast[0][i] + + d[k][10] * yDotKLast[1][i] + + d[k][11] * yDotKLast[2][i]); + } + } + + vectorsInitialized = true; + + } + + double eta = oneMinusThetaH / h; + + for (int i = 0; i < interpolatedState.length; ++i) { + interpolatedState[i] = currentState[i] + - eta * (v[0][i] + - theta * (v[1][i] + + theta * (v[2][i] + + eta * (v[3][i] + + theta * (v[4][i] + + eta * (v[5][i] + + theta * (v[6][i]))))))); + } + + } + + /** + * Really finalize the step. + * Perform the last 3 functions evaluations (k14, k15, k16) + * @throws DerivativeException this exception is propagated to the caller if the + * underlying user function triggers one + */ + protected void doFinalize() + throws DerivativeException { + + double s; + + // k14 + for (int j = 0; j < currentState.length; ++j) { + s = k14_01 * yDotK[0][j] + k14_06 * yDotK[5][j] + k14_07 * yDotK[6][j] + + k14_08 * yDotK[7][j] + k14_09 * yDotK[8][j] + k14_10 * yDotK[9][j] + + k14_11 * yDotK[10][j] + k14_12 * yDotK[11][j] + k14_13 * yDotK[12][j]; + yTmp[j] = currentState[j] + h * s; + } + equations.computeDerivatives(previousTime + c14 * h, yTmp, yDotKLast[0]); + + // k15 + for (int j = 0; j < currentState.length; ++j) { + s = k15_01 * yDotK[0][j] + k15_06 * yDotK[5][j] + k15_07 * yDotK[6][j] + + k15_08 * yDotK[7][j] + k15_09 * yDotK[8][j] + k15_10 * yDotK[9][j] + + k15_11 * yDotK[10][j] + k15_12 * yDotK[11][j] + k15_13 * yDotK[12][j] + + k15_14 * yDotKLast[0][j]; + yTmp[j] = currentState[j] + h * s; + } + equations.computeDerivatives(previousTime + c15 * h, yTmp, yDotKLast[1]); + + // k16 + for (int j = 0; j < currentState.length; ++j) { + s = k16_01 * yDotK[0][j] + k16_06 * yDotK[5][j] + k16_07 * yDotK[6][j] + + k16_08 * yDotK[7][j] + k16_09 * yDotK[8][j] + k16_10 * yDotK[9][j] + + k16_11 * yDotK[10][j] + k16_12 * yDotK[11][j] + k16_13 * yDotK[12][j] + + k16_14 * yDotKLast[0][j] + k16_15 * yDotKLast[1][j]; + yTmp[j] = currentState[j] + h * s; + } + equations.computeDerivatives(previousTime + c16 * h, yTmp, yDotKLast[2]); + + } + + /** Save the state of the instance. + * @param out stream where to save the state + * @exception IOException in case of write error + */ + public void writeExternal(ObjectOutput out) + throws IOException { + + try { + // save the local attributes + finalizeStep(); + } catch (DerivativeException e) { + IOException ioe = new IOException(); + ioe.initCause(e); + throw ioe; + } + out.writeInt(currentState.length); + for (int i = 0; i < currentState.length; ++i) { + out.writeDouble(yDotKLast[0][i]); + out.writeDouble(yDotKLast[1][i]); + out.writeDouble(yDotKLast[2][i]); + } + + // save the state of the base class + super.writeExternal(out); + + } + + /** Read the state of the instance. + * @param in stream where to read the state from + * @exception IOException in case of read error + */ + public void readExternal(ObjectInput in) + throws IOException { + + // read the local attributes + yDotKLast = new double[3][]; + int dimension = in.readInt(); + yDotKLast[0] = new double[dimension]; + yDotKLast[1] = new double[dimension]; + yDotKLast[2] = new double[dimension]; + + for (int i = 0; i < dimension; ++i) { + yDotKLast[0][i] = in.readDouble(); + yDotKLast[1][i] = in.readDouble(); + yDotKLast[2][i] = in.readDouble(); + } + + // read the base state + super.readExternal(in); + + } + + /** Last evaluations. */ + private double[][] yDotKLast; + + /** Temporary state vector. */ + private double[] yTmp; + + /** Vectors for interpolation. */ + private double[][] v; + + /** Initialization indicator for the interpolation vectors. */ + private boolean vectorsInitialized; + + // external weights of the integrator, + // note that b_02 through b_05 are null + private static double b_01 = 104257.0 / 1920240.0; + private static double b_06 = 3399327.0 / 763840.0; + private static double b_07 = 66578432.0 / 35198415.0; + private static double b_08 = -1674902723.0 / 288716400.0; + private static double b_09 = 54980371265625.0 / 176692375811392.0; + private static double b_10 = -734375.0 / 4826304.0; + private static double b_11 = 171414593.0 / 851261400.0; + private static double b_12 = 137909.0 / 3084480.0; + + // k14 for interpolation only + private static double c14 = 1.0 / 10.0; + + private static double k14_01 = 13481885573.0 / 240030000000.0 - b_01; + private static double k14_06 = 0.0 - b_06; + private static double k14_07 = 139418837528.0 / 549975234375.0 - b_07; + private static double k14_08 = -11108320068443.0 / 45111937500000.0 - b_08; + private static double k14_09 = -1769651421925959.0 / 14249385146080000.0 - b_09; + private static double k14_10 = 57799439.0 / 377055000.0 - b_10; + private static double k14_11 = 793322643029.0 / 96734250000000.0 - b_11; + private static double k14_12 = 1458939311.0 / 192780000000.0 - b_12; + private static double k14_13 = -4149.0 / 500000.0; + + // k15 for interpolation only + private static double c15 = 1.0 / 5.0; + + private static double k15_01 = 1595561272731.0 / 50120273500000.0 - b_01; + private static double k15_06 = 975183916491.0 / 34457688031250.0 - b_06; + private static double k15_07 = 38492013932672.0 / 718912673015625.0 - b_07; + private static double k15_08 = -1114881286517557.0 / 20298710767500000.0 - b_08; + private static double k15_09 = 0.0 - b_09; + private static double k15_10 = 0.0 - b_10; + private static double k15_11 = -2538710946863.0 / 23431227861250000.0 - b_11; + private static double k15_12 = 8824659001.0 / 23066716781250.0 - b_12; + private static double k15_13 = -11518334563.0 / 33831184612500.0; + private static double k15_14 = 1912306948.0 / 13532473845.0; + + // k16 for interpolation only + private static double c16 = 7.0 / 9.0; + + private static double k16_01 = -13613986967.0 / 31741908048.0 - b_01; + private static double k16_06 = -4755612631.0 / 1012344804.0 - b_06; + private static double k16_07 = 42939257944576.0 / 5588559685701.0 - b_07; + private static double k16_08 = 77881972900277.0 / 19140370552944.0 - b_08; + private static double k16_09 = 22719829234375.0 / 63689648654052.0 - b_09; + private static double k16_10 = 0.0 - b_10; + private static double k16_11 = 0.0 - b_11; + private static double k16_12 = 0.0 - b_12; + private static double k16_13 = -1199007803.0 / 857031517296.0; + private static double k16_14 = 157882067000.0 / 53564469831.0; + private static double k16_15 = -290468882375.0 / 31741908048.0; + + // interpolation weights + // (beware that only the non-null values are in the table) + private static double[][] d = { + + { -17751989329.0 / 2106076560.0, 4272954039.0 / 7539864640.0, + -118476319744.0 / 38604839385.0, 755123450731.0 / 316657731600.0, + 3692384461234828125.0 / 1744130441634250432.0, -4612609375.0 / 5293382976.0, + 2091772278379.0 / 933644586600.0, 2136624137.0 / 3382989120.0, + -126493.0 / 1421424.0, 98350000.0 / 5419179.0, + -18878125.0 / 2053168.0, -1944542619.0 / 438351368.0}, + + { 32941697297.0 / 3159114840.0, 456696183123.0 / 1884966160.0, + 19132610714624.0 / 115814518155.0, -177904688592943.0 / 474986597400.0, + -4821139941836765625.0 / 218016305204281304.0, 30702015625.0 / 3970037232.0, + -85916079474274.0 / 2800933759800.0, -5919468007.0 / 634310460.0, + 2479159.0 / 157936.0, -18750000.0 / 602131.0, + -19203125.0 / 2053168.0, 15700361463.0 / 438351368.0}, + + { 12627015655.0 / 631822968.0, -72955222965.0 / 188496616.0, + -13145744952320.0 / 69488710893.0, 30084216194513.0 / 56998391688.0, + -296858761006640625.0 / 25648977082856624.0, 569140625.0 / 82709109.0, + -18684190637.0 / 18672891732.0, 69644045.0 / 89549712.0, + -11847025.0 / 4264272.0, -978650000.0 / 16257537.0, + 519371875.0 / 6159504.0, 5256837225.0 / 438351368.0}, + + { -450944925.0 / 17550638.0, -14532122925.0 / 94248308.0, + -595876966400.0 / 2573655959.0, 188748653015.0 / 527762886.0, + 2545485458115234375.0 / 27252038150535163.0, -1376953125.0 / 36759604.0, + 53995596795.0 / 518691437.0, 210311225.0 / 7047894.0, + -1718875.0 / 39484.0, 58000000.0 / 602131.0, + -1546875.0 / 39484.0, -1262172375.0 / 8429834.0} + + }; + + private static final long serialVersionUID = 4165537490327432186L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/DummyStepHandler.java b/src/mantissa/src/org/spaceroots/mantissa/ode/DummyStepHandler.java new file mode 100644 index 000000000..b81e996c3 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/DummyStepHandler.java @@ -0,0 +1,92 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +/** + * This class is a step handler that do nothing. + + *

    This class is provided as a convenience for users who are only + * interested in the final state of an integration and not in the + * intermediate steps. Its handleStep method does nothing.

    + + *

    Since this class has no internal state, it is implemented using + * the Singleton design pattern. This means that only one instance is + * ever created, which can be retrieved using the getInstance + * method. This explains why there is no public constructor.

    + + * @see StepHandler + + * @version $Id: DummyStepHandler.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class DummyStepHandler + implements StepHandler { + + /** Private constructor. + * The constructor is private to prevent users from creating + * instances (Singleton design-pattern). + */ + private DummyStepHandler() { + } + + /** Get the only instance. + * @return the only instance + */ + public static DummyStepHandler getInstance() { + if (instance == null) { + instance = new DummyStepHandler(); + } + return instance; + } + + /** Determines whether this handler needs dense output. + * Since this handler does nothing, it does not require dense output. + * @return always false + */ + public boolean requiresDenseOutput() { + return false; + } + + /** Reset the step handler. + * Initialize the internal data as required before the first step is + * handled. + */ + public void reset() { + } + + /** + * Handle the last accepted step. + * This method does nothing in this class. + * @param interpolator interpolator for the last accepted step. For + * efficiency purposes, the various integrators reuse the same + * object on each call, so if the instance wants to keep it across + * all calls (for example to provide at the end of the integration a + * continuous model valid throughout the integration range), it + * should build a local copy using the clone method and store this + * copy. + * @param isLast true if the step is the last one + */ + public void handleStep(StepInterpolator interpolator, boolean isLast) { + } + + /** The only instance. */ + private static DummyStepHandler instance = null; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/DummyStepInterpolator.java b/src/mantissa/src/org/spaceroots/mantissa/ode/DummyStepInterpolator.java new file mode 100644 index 000000000..809c35e4a --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/DummyStepInterpolator.java @@ -0,0 +1,134 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.io.IOException; + +/** This class is a step interpolator that does nothing. + * + *

    This class is used when the {@link StepHandler "step handler"} + * set up by the user does not need step interpolation. It does not + * recompute the state when {@link AbstractStepInterpolator#setInterpolatedTime + * setInterpolatedTime} is called. This implies the interpolated state + * is always the state at the end of the current step.

    + * + * @see StepHandler + * + * @version $Id: DummyStepInterpolator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + * + */ + +public class DummyStepInterpolator + extends AbstractStepInterpolator { + + /** Simple constructor. + * This constructor builds an instance that is not usable yet, the + * {@link AbstractStepInterpolator#reinitialize} method should be called + * before using the instance in order to initialize the internal arrays. This + * constructor is used only in order to delay the initialization in + * some cases. As an example, the {@link + * RungeKuttaFehlbergIntegrator} uses the prototyping design pattern + * to create the step interpolators by cloning an uninitialized + * model and latter initializing the copy. + */ + protected DummyStepInterpolator() { + super(); + } + + /** Simple constructor. + * @param y reference to the integrator array holding the state at + * the end of the step + * @param forward integration direction indicator + */ + protected DummyStepInterpolator(double[] y, boolean forward) { + super(y, forward); + } + + /** Copy constructor. + + *

    The copied interpolator should have been finalized before the + * copy, otherwise the copy will not be able to perform correctly + * any interpolation and will throw a {@link NullPointerException} + * later. Since we don't want this constructor to throw the + * exceptions finalization may involve and since we don't want this + * method to modify the state of the copied interpolator, + * finalization is not done automatically, it + * remains under user control.

    + + *

    The copy is a deep copy: its arrays are separated from the + * original arrays of the instance.

    + + * @param interpolator interpolator to copy from. + + */ + protected DummyStepInterpolator(DummyStepInterpolator interpolator) { + super(interpolator); + } + + /** Copy the instance. + * the copy is a deep copy: its arrays are separated from the + * original arrays of the instance + * @return a copy of the instance. + */ + public Object clone() { + return new DummyStepInterpolator(this); + } + + /** Compute the state at the interpolated time. + * In this class, this method does nothing: the interpolated state + * is always the state at the end of the current step. + * @param theta normalized interpolation abscissa within the step + * (theta is zero at the previous time step and one at the current time step) + * @param oneMinusThetaH time gap between the interpolated time and + * the current time + * @throws DerivativeException this exception is propagated to the caller if the + * underlying user function triggers one + */ + protected void computeInterpolatedState(double theta, double oneMinusThetaH) + throws DerivativeException { + } + + public void writeExternal(ObjectOutput out) + throws IOException { + // save the state of the base class + writeBaseExternal(out); + } + + public void readExternal(ObjectInput in) + throws IOException { + + // read the base class + double t = readBaseExternal(in); + + try { + // we can now set the interpolated time and state + setInterpolatedTime(t); + } catch (DerivativeException e) { + IOException ioe = new IOException(); + ioe.initCause(e); + throw ioe; + } + + } + + private static final long serialVersionUID = 1708010296707839488L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/EulerIntegrator.java b/src/mantissa/src/org/spaceroots/mantissa/ode/EulerIntegrator.java new file mode 100644 index 000000000..b137b2596 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/EulerIntegrator.java @@ -0,0 +1,80 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +/** + * This class implements a simple Euler integrator for Ordinary + * Differential Equations. + + *

    The Euler algorithm is the simplest one that can be used to + * integrate ordinary differential equations. It is a simple inversion + * of the forward difference expression : + * f'=(f(t+h)-f(t))/h which leads to + * f(t+h)=f(t)+hf'. The interpolation scheme used for + * dense output is the linear scheme already used for integration.

    + + *

    This algorithm looks cheap because it needs only one function + * evaluation per step. However, as it uses linear estimates, it needs + * very small steps to achieve high accuracy, and small steps lead to + * numerical errors and instabilities.

    + + *

    This algorithm is almost never used and has been included in + * this package only as a comparison reference for more useful + * integrators.

    + + * @see MidpointIntegrator + * @see ClassicalRungeKuttaIntegrator + * @see GillIntegrator + * @see ThreeEighthesIntegrator + + * @version $Id: EulerIntegrator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class EulerIntegrator + extends RungeKuttaIntegrator { + + private static final String methodName = new String("Euler"); + + private static final double[] c = { + }; + + private static final double[][] a = { + }; + + private static final double[] b = { + 1.0 + }; + + /** Simple constructor. + * Build an Euler integrator with the given step. + * @param step integration step + */ + public EulerIntegrator(double step) { + super(false, c, a, b, new EulerStepInterpolator(), step); + } + + /** Get the name of the method. + * @return name of the method + */ + public String getName() { + return methodName; + } + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/EulerStepInterpolator.java b/src/mantissa/src/org/spaceroots/mantissa/ode/EulerStepInterpolator.java new file mode 100644 index 000000000..3206c3674 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/EulerStepInterpolator.java @@ -0,0 +1,97 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +/** + * This class implements a linear interpolator for step. + + *

    This interpolator allow to compute dense output inside the last + * step computed. The interpolation equation is consistent with the + * integration scheme : + + *

    + *   y(t_n + theta h) = y (t_n + h) - (1-theta) h y'
    + * 
    + + * where theta belongs to [0 ; 1] and where y' is the evaluation of + * the derivatives already computed during the step.

    + + * @see EulerIntegrator + + * @version $Id: EulerStepInterpolator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +class EulerStepInterpolator + extends RungeKuttaStepInterpolator { + + /** Simple constructor. + * This constructor builds an instance that is not usable yet, the + * {@link AbstractStepInterpolator#reinitialize} method should be called + * before using the instance in order to initialize the internal arrays. This + * constructor is used only in order to delay the initialization in + * some cases. The {@link RungeKuttaIntegrator} class uses the + * prototyping design pattern to create the step interpolators by + * cloning an uninitialized model and latter initializing the copy. + */ + public EulerStepInterpolator() { + } + + /** Copy constructor. + * @param interpolator interpolator to copy from. The copy is a deep + * copy: its arrays are separated from the original arrays of the + * instance + */ + public EulerStepInterpolator(EulerStepInterpolator interpolator) { + super(interpolator); + } + + /** + * Clone the instance. + * the copy is a deep copy: its arrays are separated from the + * original arrays of the instance + * @return a copy of the instance + */ + public Object clone() { + return new EulerStepInterpolator(this); + } + + /** Compute the state at the interpolated time. + * This is the main processing method that should be implemented by + * the derived classes to perform the interpolation. + * @param theta normalized interpolation abscissa within the step + * (theta is zero at the previous time step and one at the current time step) + * @param oneMinusThetaH time gap between the interpolated time and + * the current time + * @throws DerivativeException this exception is propagated to the caller if the + * underlying user function triggers one + */ + protected void computeInterpolatedState(double theta, + double oneMinusThetaH) + throws DerivativeException { + + for (int i = 0; i < interpolatedState.length; ++i) { + interpolatedState[i] = currentState[i] - oneMinusThetaH * yDotK[0][i]; + } + + } + + private static final long serialVersionUID = -7179861704951334960L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/FirstOrderConverter.java b/src/mantissa/src/org/spaceroots/mantissa/ode/FirstOrderConverter.java new file mode 100644 index 000000000..d26dc0dc6 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/FirstOrderConverter.java @@ -0,0 +1,108 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +/** This class converts second order differential equations to first + * order ones. + + *

    This class is a wrapper around a {@link + * SecondOrderDifferentialEquations} which allow to use a {@link + * FirstOrderIntegrator} to integrate it.

    + + *

    The transformation is done by changing the n dimension state + * vector to a 2n dimension vector, where the first n components are + * the initial state variables and the n last components are their + * first time derivative. The first time derivative of this state + * vector then really contains both the first and second time + * derivative of the initial state vector, which can be handled by the + * underlying second order equations set.

    + + *

    One should be aware that the data is duplicated during the + * transformation process and that for each call to {@link + * #computeDerivatives computeDerivatives}, this wrapper does copy 4n + * scalars : 2n before the call to {@link + * SecondOrderDifferentialEquations#computeSecondDerivatives + * computeSecondDerivatives} in order to dispatch the y state vector + * into z and zDot, and 2n after the call to gather zDot and zDDot + * into yDot. Since the underlying problem by itself perhaps also + * needs to copy data and dispatch the arrays into domain objects, + * this has an impact on both memory and CPU usage. The only way to + * avoid this duplication is to perform the transformation at the + * problem level, i.e. to implement the problem as a first order one + * and then avoid using this class.

    + + * @see FirstOrderIntegrator + * @see FirstOrderDifferentialEquations + * @see SecondOrderDifferentialEquations + + * @version $Id: FirstOrderConverter.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class FirstOrderConverter + implements FirstOrderDifferentialEquations { + + /** Simple constructor. + * Build a converter around a second order equations set. + * @param equations second order equations set to convert + */ + public FirstOrderConverter (SecondOrderDifferentialEquations equations) { + this.equations = equations; + dimension = equations.getDimension(); + z = new double[dimension]; + zDot = new double[dimension]; + zDDot = new double[dimension]; + } + + public int getDimension() { + return 2 * dimension; + } + + public void computeDerivatives(double t, double[] y, double[] yDot) + throws DerivativeException { + + // split the state vector in two + System.arraycopy(y, 0, z, 0, dimension); + System.arraycopy(y, dimension, zDot, 0, dimension); + + // apply the underlying equations set + equations.computeSecondDerivatives(t, z, zDot, zDDot); + + // build the result state derivative + System.arraycopy(zDot, 0, yDot, 0, dimension); + System.arraycopy(zDDot, 0, yDot, dimension, dimension); + + } + + /** Underlying second order equations set. */ + private SecondOrderDifferentialEquations equations; + + /** second order problem dimension. */ + private int dimension; + + /** state vector. */ + private double[] z; + + /** first time derivative of the state vector. */ + private double[] zDot; + + /** second time derivative of the state vector. */ + private double[] zDDot; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/FirstOrderDifferentialEquations.java b/src/mantissa/src/org/spaceroots/mantissa/ode/FirstOrderDifferentialEquations.java new file mode 100644 index 000000000..ba1181b44 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/FirstOrderDifferentialEquations.java @@ -0,0 +1,66 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +/** This interface represents a first order differential equations set. + * + *

    This interface should be implemented by all real first order + * differential equation problems before they can be handled by the + * integrators {@link FirstOrderIntegrator#integrate} method.

    + * + *

    A first order differential equations problem, as seen by an + * integrator is the time derivative dY/dt of a state + * vector Y, both being one dimensional arrays. From the + * integrator point of view, this derivative depends only on the + * current time t and on the state vector + * Y.

    + * + *

    For real problems, the derivative depends also on parameters + * that do not belong to the state vector (dynamical model constants + * for example). These constants are completely outside of the scope + * of this interface, the classes that implement it are allowed to + * handle them as they want.

    + * + * @see FirstOrderIntegrator + * @see FirstOrderConverter + * @see SecondOrderDifferentialEquations + * @see org.spaceroots.mantissa.utilities.ArraySliceMappable + * + * @version $Id: FirstOrderDifferentialEquations.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + * + */ + +public interface FirstOrderDifferentialEquations { + + /** Get the dimension of the problem. + * @return dimension of the problem + */ + public int getDimension(); + + /** Get the current time derivative of the state vector. + * @param t current value of the independant time variable + * @param y array containing the current value of the state vector + * @param yDot placeholder array where to put the time derivative of the state vector + * @throws DerivativeException this exception is propagated to the caller if the + * underlying user function triggers one + */ + public void computeDerivatives(double t, double[] y, double[] yDot) + throws DerivativeException; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/FirstOrderIntegrator.java b/src/mantissa/src/org/spaceroots/mantissa/ode/FirstOrderIntegrator.java new file mode 100644 index 000000000..9b9a10bdf --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/FirstOrderIntegrator.java @@ -0,0 +1,84 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +/** This interface represents a first order integrator for + * differential equations. + + *

    The classes which are devoted to solve first order differential + * equations should implement this interface. The problems which can + * be handled should implement the {@link + * FirstOrderDifferentialEquations} interface.

    + + * @see FirstOrderDifferentialEquations + * @see StepHandler + * @see SwitchingFunction + + * @version $Id: FirstOrderIntegrator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public interface FirstOrderIntegrator { + + /** Get the name of the method. + * @return name of the method + */ + public String getName(); + + /** Set the step handler for this integrator. + * The handler will be called by the integrator for each accepted + * step. + * @param handler handler for the accepted steps + */ + public void setStepHandler (StepHandler handler); + + /** Get the step handler for this integrator. + * @return the step handler for this integrator + */ + public StepHandler getStepHandler(); + + /** Add a switching function to the integrator. + * @param function switching function + * @param maxCheckInterval maximal time interval between switching + * function checks (this interval prevents missing sign changes in + * case the integration steps becomes very large) + * @param convergence convergence threshold in the event time search + */ + public void addSwitchingFunction(SwitchingFunction function, + double maxCheckInterval, + double convergence); + + /** Integrate the differential equations up to the given time + * @param equations differential equations to integrate + * @param t0 initial time + * @param y0 initial value of the state vector at t0 + * @param t target time for the integration + * (can be set to a value smaller thant t0 for backward integration) + * @param y placeholder where to put the state vector at each successful + * step (and hence at the end of integration), can be the same object as y0 + * @throws IntegratorException if the integrator cannot perform integration + * @throws DerivativeException this exception is propagated to the caller if + * the underlying user function triggers one + */ + public void integrate (FirstOrderDifferentialEquations equations, + double t0, double[] y0, + double t, double[] y) + throws DerivativeException, IntegratorException; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/FixedStepHandler.java b/src/mantissa/src/org/spaceroots/mantissa/ode/FixedStepHandler.java new file mode 100644 index 000000000..e20584eb5 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/FixedStepHandler.java @@ -0,0 +1,57 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +/** + * This interface represents a handler that should be called after + * each successful fixed step. + + *

    This interface should be implemented by anyone who is interested + * in getting the solution of an ordinary differential equation at + * fixed time steps. Objects implementing this interface should be + * wrapped within an instance of {@link StepNormalizer} that itself + * is used as the general {@link StepHandler} by the integrator. The + * {@link StepNormalizer} object is called according to the integrator + * internal algorithms and it calls objects implementing this + * interface as necessary at fixed time steps.

    + + * @see StepHandler + * @see StepNormalizer + + * @version $Id: FixedStepHandler.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public interface FixedStepHandler { + + /** + * Handle the last accepted step + * @param t time of the current step + + * @param y state vector at t. For efficiency purposes, the {@link + * StepNormalizer} class reuse the same array on each call, so if + * the instance wants to keep it across all calls (for example to + * provide at the end of the integration a complete array of all + * steps), it should build a local copy store this copy. + + * @param isLast true if the step is the last one + */ + public void handleStep(double t, double[] y, boolean isLast); + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/GillIntegrator.java b/src/mantissa/src/org/spaceroots/mantissa/ode/GillIntegrator.java new file mode 100644 index 000000000..9420c15d8 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/GillIntegrator.java @@ -0,0 +1,82 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +/** + * This class implements the Gill fourth order Runge-Kutta + * integrator for Ordinary Differential Equations . + + *

    This method is an explicit Runge-Kutta method, its Butcher-array + * is the following one : + *

    + *    0  |    0        0       0      0
    + *   1/2 |   1/2       0       0      0
    + *   1/2 | (q-1)/2  (2-q)/2    0      0
    + *    1  |    0       -q/2  (2+q)/2   0
    + *       |-------------------------------
    + *       |   1/6    (2-q)/6 (2+q)/6  1/6
    + * 
    + * where q = sqrt(2)

    + + * @see EulerIntegrator + * @see ClassicalRungeKuttaIntegrator + * @see MidpointIntegrator + * @see ThreeEighthesIntegrator + + * @version $Id: GillIntegrator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class GillIntegrator + extends RungeKuttaIntegrator { + + private static final String methodName = new String("Gill"); + + private static final double sqrt2 = Math.sqrt(2.0); + + private static final double[] c = { + 1.0 / 2.0, 1.0 / 2.0, 1.0 + }; + + private static final double[][] a = { + { 1.0 / 2.0 }, + { (sqrt2 - 1.0) / 2.0, (2.0 - sqrt2) / 2.0 }, + { 0.0, -sqrt2 / 2.0, (2.0 + sqrt2) / 2.0 } + }; + + private static final double[] b = { + 1.0 / 6.0, (2.0 - sqrt2) / 6.0, (2.0 + sqrt2) / 6.0, 1.0 / 6.0 + }; + + /** Simple constructor. + * Build a fourth-order Gill integrator with the given step. + * @param step integration step + */ + public GillIntegrator(double step) { + super(false, c, a, b, new GillStepInterpolator(), step); + } + + /** Get the name of the method. + * @return name of the method + */ + public String getName() { + return methodName; + } + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/GillStepInterpolator.java b/src/mantissa/src/org/spaceroots/mantissa/ode/GillStepInterpolator.java new file mode 100644 index 000000000..c8e2dd21f --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/GillStepInterpolator.java @@ -0,0 +1,119 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +/** + * This class implements a step interpolator for the Gill fourth + * order Runge-Kutta integrator. + + *

    This interpolator allows to compute dense output inside the last + * step computed. The interpolation equation is consistent with the + * integration scheme : + + *

    + *   y(t_n + theta h) = y (t_n + h)
    + *                    - (1 - theta) (h/6) [ (1 - theta) (1 - 4 theta) y'_1
    + *                                        + (1 - theta) (1 + 2 theta) ((2-q) y'_2 + (2+q) y'_3)
    + *                                        + (1 + theta + 4 theta^2) y'_4
    + *                                        ]
    + * 
    + * where theta belongs to [0 ; 1], q = sqrt(2) and where y'_1 to y'_4 + * are the four evaluations of the derivatives already computed during + * the step.

    + + * @see GillIntegrator + + * @version $Id: GillStepInterpolator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +class GillStepInterpolator + extends RungeKuttaStepInterpolator { + + /** Simple constructor. + * This constructor builds an instance that is not usable yet, the + * {@link AbstractStepInterpolator#reinitialize} method should be called + * before using the instance in order to initialize the internal arrays. This + * constructor is used only in order to delay the initialization in + * some cases. The {@link RungeKuttaIntegrator} class uses the + * prototyping design pattern to create the step interpolators by + * cloning an uninitialized model and latter initializing the copy. + */ + public GillStepInterpolator() { + } + + /** Copy constructor. + * @param interpolator interpolator to copy from. The copy is a deep + * copy: its arrays are separated from the original arrays of the + * instance + */ + public GillStepInterpolator(GillStepInterpolator interpolator) { + super(interpolator); + } + + /** + * Clone the instance. + * the copy is a deep copy: its arrays are separated from the + * original arrays of the instance + * @return a copy of the instance + */ + public Object clone() { + return new GillStepInterpolator(this); + } + + /** Compute the state at the interpolated time. + * This is the main processing method that should be implemented by + * the derived classes to perform the interpolation. + * @param theta normalized interpolation abscissa within the step + * (theta is zero at the previous time step and one at the current time step) + * @param oneMinusThetaH time gap between the interpolated time and + * the current time + * @throws DerivativeException this exception is propagated to the caller if the + * underlying user function triggers one + */ + protected void computeInterpolatedState(double theta, + double oneMinusThetaH) + throws DerivativeException { + + double fourTheta = 4 * theta; + double s = oneMinusThetaH / 6.0; + double soMt = s * (1 - theta); + double c23 = soMt * (1 + 2 * theta); + double coeff1 = soMt * (1 - fourTheta); + double coeff2 = c23 * tMq; + double coeff3 = c23 * tPq; + double coeff4 = s * (1 + theta * (1 + fourTheta)); + + for (int i = 0; i < interpolatedState.length; ++i) { + interpolatedState[i] = currentState[i] + - coeff1 * yDotK[0][i] - coeff2 * yDotK[1][i] + - coeff3 * yDotK[2][i] - coeff4 * yDotK[3][i]; + } + + } + + /** First Gill coefficient. */ + private static final double tMq = 2 - Math.sqrt(2.0); + + /** Second Gill coefficient. */ + private static final double tPq = 2 + Math.sqrt(2.0); + + private static final long serialVersionUID = -107804074496313322L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/GraggBulirschStoerIntegrator.java b/src/mantissa/src/org/spaceroots/mantissa/ode/GraggBulirschStoerIntegrator.java new file mode 100644 index 000000000..32c182986 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/GraggBulirschStoerIntegrator.java @@ -0,0 +1,1016 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +/** + * This class implements a Gragg-Bulirsch-Stoer integrator for + * Ordinary Differential Equations. + + *

    The Gragg-Bulirsch-Stoer algorithm is one of the most efficient + * ones currently available for smooth problems. It uses Richardson + * extrapolation to estimate what would be the solution if the step + * size could be decreased down to zero.

    + + *

    + * This method changes both the step size and the order during + * integration, in order to minimize computation cost. It is + * particularly well suited when a very high precision is needed. The + * limit where this method becomes more efficient than high-order + * Runge-Kutta-Fehlberg methods like {@link DormandPrince853Integrator + * Dormand-Prince 8(5,3)} depends on the problem. Results given in the + * Hairer, Norsett and Wanner book show for example that this limit + * occurs for accuracy around 1e-6 when integrating Saltzam-Lorenz + * equations (the authors note this problem is extremely sensitive + * to the errors in the first integration steps), and around 1e-11 + * for a two dimensional celestial mechanics problems with seven + * bodies (pleiades problem, involving quasi-collisions for which + * automatic step size control is essential). + *

    + + *

    + * This implementation is basically a reimplementation in Java of the + * odex + * fortran code by E. Hairer and G. Wanner. The redistribution policy + * for this code is available here, for + * convenience, it is reproduced below.

    + *

    + + * + * + + * + + * + *
    Copyright (c) 2004, Ernst Hairer
    Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + *
      + *
    • Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer.
    • + *
    • Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution.
    • + *
    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    + + * @author E. Hairer and G. Wanner (fortran version) + * @author L. Maisonobe (Java port) + * @version $Id: GraggBulirschStoerIntegrator.java 1705 2006-09-17 19:57:39Z luc $ + + */ + +public class GraggBulirschStoerIntegrator + extends AdaptiveStepsizeIntegrator { + + private static final String methodName = new String("Gragg-Bulirsch-Stoer"); + + /** Simple constructor. + * Build a Gragg-Bulirsch-Stoer integrator with the given step + * bounds. All tuning parameters are set to their default + * values. The default step handler does nothing. + * @param minStep minimal step (must be positive even for backward + * integration), the last step can be smaller than this + * @param maxStep maximal step (must be positive even for backward + * integration) + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + */ + public GraggBulirschStoerIntegrator(double minStep, double maxStep, + double scalAbsoluteTolerance, + double scalRelativeTolerance) { + super(minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance); + denseOutput = (handler.requiresDenseOutput() + || (! switchesHandler.isEmpty())); + setStabilityCheck(true, -1, -1, -1); + setStepsizeControl(-1, -1, -1, -1); + setOrderControl(-1, -1, -1); + setInterpolationControl(true, -1); + } + + /** Simple constructor. + * Build a Gragg-Bulirsch-Stoer integrator with the given step + * bounds. All tuning parameters are set to their default + * values. The default step handler does nothing. + * @param minStep minimal step (must be positive even for backward + * integration), the last step can be smaller than this + * @param maxStep maximal step (must be positive even for backward + * integration) + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + */ + public GraggBulirschStoerIntegrator(double minStep, double maxStep, + double[] vecAbsoluteTolerance, + double[] vecRelativeTolerance) { + super(minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance); + denseOutput = (handler.requiresDenseOutput() + || (! switchesHandler.isEmpty())); + setStabilityCheck(true, -1, -1, -1); + setStepsizeControl(-1, -1, -1, -1); + setOrderControl(-1, -1, -1); + setInterpolationControl(true, -1); + } + + /** Set the stability check controls. + *

    The stability check is performed on the first few iterations of + * the extrapolation scheme. If this test fails, the step is rejected + * and the stepsize is reduced.

    + *

    By default, the test is performed, at most during two + * iterations at each step, and at most once for each of these + * iterations. The default stepsize reduction factor is 0.5.

    + * @param performTest if true, stability check will be performed, + if false, the check will be skipped + * @param maxIter maximal number of iterations for which checks are + * performed (the number of iterations is reset to default if negative + * or null) + * @param maxChecks maximal number of checks for each iteration + * (the number of checks is reset to default if negative or null) + * @param stabilityReduction stepsize reduction factor in case of + * failure (the factor is reset to default if lower than 0.0001 or + * greater than 0.9999) + */ + public void setStabilityCheck(boolean performTest, + int maxIter, int maxChecks, + double stabilityReduction) { + + this.performTest = performTest; + this.maxIter = (maxIter <= 0) ? 2 : maxIter; + this.maxChecks = (maxChecks <= 0) ? 1 : maxChecks; + + if ((stabilityReduction < 0.0001) || (stabilityReduction > 0.9999)) { + this.stabilityReduction = 0.5; + } else { + this.stabilityReduction = stabilityReduction; + } + + } + + /** Set the step size control factors. + + *

    The new step size hNew is computed from the old one h by: + *

    +   * hNew = h * stepControl2 / (err/stepControl1)^(1/(2k+1))
    +   * 
    + * where err is the scaled error and k the iteration number of the + * extrapolation scheme (counting from 0). The default values are + * 0.65 for stepControl1 and 0.94 for stepControl2.

    + *

    The step size is subject to the restriction: + *

    +   * stepControl3^(1/(2k+1))/stepControl4 <= hNew/h <= 1/stepControl3^(1/(2k+1))
    +   * 
    + * The default values are 0.02 for stepControl3 and 4.0 for + * stepControl4.

    + * @param stepControl1 first stepsize control factor (the factor is + * reset to default if lower than 0.0001 or greater than 0.9999) + * @param stepControl2 second stepsize control factor (the factor + * is reset to default if lower than 0.0001 or greater than 0.9999) + * @param stepControl3 third stepsize control factor (the factor is + * reset to default if lower than 0.0001 or greater than 0.9999) + * @param stepControl4 fourth stepsize control factor (the factor + * is reset to default if lower than 1.0001 or greater than 999.9) + */ + public void setStepsizeControl(double stepControl1, double stepControl2, + double stepControl3, double stepControl4) { + + if ((stepControl1 < 0.0001) || (stepControl1 > 0.9999)) { + this.stepControl1 = 0.65; + } else { + this.stepControl1 = stepControl1; + } + + if ((stepControl2 < 0.0001) || (stepControl2 > 0.9999)) { + this.stepControl2 = 0.94; + } else { + this.stepControl2 = stepControl2; + } + + if ((stepControl3 < 0.0001) || (stepControl3 > 0.9999)) { + this.stepControl3 = 0.02; + } else { + this.stepControl3 = stepControl3; + } + + if ((stepControl4 < 1.0001) || (stepControl4 > 999.9)) { + this.stepControl4 = 4.0; + } else { + this.stepControl4 = stepControl4; + } + + } + + /** Set the order control parameters. + *

    The Gragg-Bulirsch-Stoer method changes both the step size and + * the order during integration, in order to minimize computation + * cost. Each extrapolation step increases the order by 2, so the + * maximal order that will be used is always even, it is twice the + * maximal number of columns in the extrapolation table.

    + *
    +   * order is decreased if w(k-1) <= w(k)   * orderControl1
    +   * order is increased if w(k)   <= w(k-1) * orderControl2
    +   * 
    + *

    where w is the table of work per unit step for each order + * (number of function calls divided by the step length), and k is + * the current order.

    + *

    The default maximal order after construction is 18 (i.e. the + * maximal number of columns is 9). The default values are 0.8 for + * orderControl1 and 0.9 for orderControl2.

    + * @param maxOrder maximal order in the extrapolation table (the + * maximal order is reset to default if order <= 6 or odd) + * @param orderControl1 first order control factor (the factor is + * reset to default if lower than 0.0001 or greater than 0.9999) + * @param orderControl2 second order control factor (the factor + * is reset to default if lower than 0.0001 or greater than 0.9999) + */ + public void setOrderControl(int maxOrder, + double orderControl1, double orderControl2) { + + if ((maxOrder <= 6) || (maxOrder % 2 != 0)) { + this.maxOrder = 18; + } + + if ((orderControl1 < 0.0001) || (orderControl1 > 0.9999)) { + this.orderControl1 = 0.8; + } else { + this.orderControl1 = orderControl1; + } + + if ((orderControl2 < 0.0001) || (orderControl2 > 0.9999)) { + this.orderControl2 = 0.9; + } else { + this.orderControl2 = orderControl2; + } + + // reinitialize the arrays + initializeArrays(); + + } + + /** Set the step handler for this integrator. + * The handler will be called by the integrator for each accepted + * step. + * @param handler handler for the accepted steps + */ + public void setStepHandler (StepHandler handler) { + + super.setStepHandler(handler); + denseOutput = (handler.requiresDenseOutput() + || (! switchesHandler.isEmpty())); + + // reinitialize the arrays + initializeArrays(); + + } + + /** Add a switching function to the integrator. + * @param function switching function + * @param maxCheckInterval maximal time interval between switching + * function checks (this interval prevents missing sign changes in + * case the integration steps becomes very large) + * @param convergence convergence threshold in the event time search + */ + public void addSwitchingFunction(SwitchingFunction function, + double maxCheckInterval, + double convergence) { + super.addSwitchingFunction(function, maxCheckInterval, convergence); + denseOutput = (handler.requiresDenseOutput() + || (! switchesHandler.isEmpty())); + + // reinitialize the arrays + initializeArrays(); + + } + + /** Initialize the integrator internal arrays. */ + private void initializeArrays() { + + int size = maxOrder / 2; + + if ((sequence == null) || (sequence.length != size)) { + // all arrays should be reallocated with the right size + sequence = new int[size]; + costPerStep = new int[size]; + coeff = new double[size][]; + costPerTimeUnit = new double[size]; + optimalStep = new double[size]; + } + + if (denseOutput) { + // step size sequence: 2, 6, 10, 14, ... + for (int k = 0; k < size; ++k) { + sequence[k] = 4 * k + 2; + } + } else { + // step size sequence: 2, 4, 6, 8, ... + for (int k = 0; k < size; ++k) { + sequence[k] = 2 * (k + 1); + } + } + + // initialize the order selection cost array + // (number of function calls for each column of the extrapolation table) + costPerStep[0] = sequence[0] + 1; + for (int k = 1; k < size; ++k) { + costPerStep[k] = costPerStep[k-1] + sequence[k]; + } + + // initialize the extrapolation tables + for (int k = 0; k < size; ++k) { + coeff[k] = (k > 0) ? new double[k] : null; + for (int l = 0; l < k; ++l) { + double ratio = ((double) sequence[k]) / sequence[k-l-1]; + coeff[k][l] = 1.0 / (ratio * ratio - 1.0); + } + } + + } + + /** Set the interpolation order control parameter. + * The interpolation order for dense output is 2k - mudif + 1. The + * default value for mudif is 4 and the interpolation error is used + * in stepsize control by default. + + * @param useInterpolationError if true, interpolation error is used + * for stepsize control + * @param mudif interpolation order control parameter (the parameter + * is reset to default if <= 0 or >= 7) + */ + public void setInterpolationControl(boolean useInterpolationError, + int mudif) { + + this.useInterpolationError = useInterpolationError; + + if ((mudif <= 0) || (mudif >= 7)) { + this.mudif = 4; + } else { + this.mudif = mudif; + } + + } + + /** Get the name of the method. + * @return name of the method + */ + public String getName() { + return methodName; + } + + /** Update scaling array. + * @param y1 first state vector to use for scaling + * @param y2 second state vector to use for scaling + * @param scale scaling array to update + */ + private void rescale(double[] y1, double[] y2, double[] scale) { + if (vecAbsoluteTolerance == null) { + for (int i = 0; i < scale.length; ++i) { + double yi = Math.max(Math.abs(y1[i]), Math.abs(y2[i])); + scale[i] = scalAbsoluteTolerance + scalRelativeTolerance * yi; + } + } else { + for (int i = 0; i < scale.length; ++i) { + double yi = Math.max(Math.abs(y1[i]), Math.abs(y2[i])); + scale[i] = vecAbsoluteTolerance[i] + vecRelativeTolerance[i] * yi; + } + } + } + + /** Perform integration over one step using substeps of a modified + * midpoint method. + * @param equations differential equations to integrate + * @param t0 initial time + * @param y0 initial value of the state vector at t0 + * @param step global step + * @param k iteration number (from 0 to sequence.length - 1) + * @param scale scaling array + * @param f placeholder where to put the state vector derivatives at each substep + * (element 0 already contains initial derivative) + * @param yMiddle placeholder where to put the state vector at the middle of the step + * @param yEnd placeholder where to put the state vector at the end + * @param yTmp placeholder for one state vector + * @return true if computation was done properly, + * false if stability check failed before end of computation + * @throws DerivativeException this exception is propagated to the caller if the + * underlying user function triggers one + */ + private boolean tryStep(FirstOrderDifferentialEquations equations, + double t0, double[] y0, double step, int k, + double[] scale, double[][] f, + double[] yMiddle, double[] yEnd, + double[] yTmp) + throws DerivativeException { + + int n = sequence[k]; + double subStep = step / n; + double subStep2 = 2 * subStep; + + // first substep + double t = t0 + subStep; + for (int i = 0; i < y0.length; ++i) { + yTmp[i] = y0[i]; + yEnd[i] = y0[i] + subStep * f[0][i]; + } + equations.computeDerivatives(t, yEnd, f[1]); + + // other substeps + for (int j = 1; j < n; ++j) { + + if (2 * j == n) { + // save the point at the middle of the step + System.arraycopy(yEnd, 0, yMiddle, 0, y0.length); + } + + t += subStep; + for (int i = 0; i < y0.length; ++i) { + double middle = yEnd[i]; + yEnd[i] = yTmp[i] + subStep2 * f[j][i]; + yTmp[i] = middle; + } + + equations.computeDerivatives(t, yEnd, f[j+1]); + + // stability check + if (performTest && (j <= maxChecks) && (k < maxIter)) { + double initialNorm = 0.0; + for (int l = 0; l < y0.length; ++l) { + double ratio = f[0][l] / scale[l]; + initialNorm += ratio * ratio; + } + double deltaNorm = 0.0; + for (int l = 0; l < y0.length; ++l) { + double ratio = (f[j+1][l] - f[0][l]) / scale[l]; + deltaNorm += ratio * ratio; + } + if (deltaNorm > 4 * Math.max(1.0e-15, initialNorm)) { + return false; + } + } + + } + + // correction of the last substep (at t0 + step) + for (int i = 0; i < y0.length; ++i) { + yEnd[i] = 0.5 * (yTmp[i] + yEnd[i] + subStep * f[n][i]); + } + + return true; + + } + + /** Extrapolate a vector. + * @param offset offset to use in the coefficients table + * @param k index of the last updated point + * @param diag working diagonal of the Aitken-Neville's + * triangle, without the last element + * @param last last element + */ + private void extrapolate(int offset, int k, double[][] diag, double[] last) { + + // update the diagonal + for (int j = 1; j < k; ++j) { + for (int i = 0; i < last.length; ++i) { + // Aitken-Neville's recursive formula + diag[k-j-1][i] = diag[k-j][i] + + coeff[k+offset][j-1] * (diag[k-j][i] - diag[k-j-1][i]); + } + } + + // update the last element + for (int i = 0; i < last.length; ++i) { + // Aitken-Neville's recursive formula + last[i] = diag[0][i] + coeff[k+offset][k-1] * (diag[0][i] - last[i]); + } + } + + public void integrate(FirstOrderDifferentialEquations equations, + double t0, double[] y0, double t, double[] y) + throws DerivativeException, IntegratorException { + + // sanity check + if (equations.getDimension() != y0.length) { + throw new IntegratorException("dimensions mismatch: " + + "ODE problem has dimension {0}" + + ", state vector has dimension {1}", + new String[] { + Integer.toString(equations.getDimension()), + Integer.toString(y0.length) + }); + } + if (Math.abs(t - t0) <= 1.0e-12 * Math.max(Math.abs(t0), Math.abs(t))) { + throw new IntegratorException("too small integration interval: length = {0}", + new String[] { + Double.toString(Math.abs(t - t0)) + }); + } + + boolean forward = (t > t0); + + // create some internal working arrays + double[] yDot0 = new double[y0.length]; + double[] y1 = new double[y0.length]; + double[] yTmp = new double[y0.length]; + double[] yTmpDot = new double[y0.length]; + + double[][] diagonal = new double[sequence.length-1][]; + double[][] y1Diag = new double[sequence.length-1][]; + for (int k = 0; k < sequence.length-1; ++k) { + diagonal[k] = new double[y0.length]; + y1Diag[k] = new double[y0.length]; + } + + double[][][] fk = new double[sequence.length][][]; + for (int k = 0; k < sequence.length; ++k) { + + fk[k] = new double[sequence[k] + 1][]; + + // all substeps start at the same point, so share the first array + fk[k][0] = yDot0; + + for (int l = 0; l < sequence[k]; ++l) { + fk[k][l+1] = new double[y0.length]; + } + + } + + if (y != y0) { + System.arraycopy(y0, 0, y, 0, y0.length); + } + + double[] yDot1 = null; + double[][] yMidDots = null; + if (denseOutput) { + yDot1 = new double[y0.length]; + yMidDots = new double[1 + 2 * sequence.length][]; + for (int j = 0; j < yMidDots.length; ++j) { + yMidDots[j] = new double[y0.length]; + } + } else { + yMidDots = new double[1][]; + yMidDots[0] = new double[y0.length]; + } + + // initial scaling + double[] scale = new double[y0.length]; + rescale(y, y, scale); + + // initial order selection + double log10R = Math.log(Math.max(1.0e-10, + (vecRelativeTolerance == null) + ? scalRelativeTolerance + : vecRelativeTolerance[0])) + / Math.log(10.0); + int targetIter = Math.max(1, + Math.min(sequence.length - 2, + (int) Math.floor(0.5 - 0.6 * log10R))); + // set up an interpolator sharing the integrator arrays + AbstractStepInterpolator interpolator = null; + if (denseOutput || (! switchesHandler.isEmpty())) { + interpolator = new GraggBulirschStoerStepInterpolator(y, yDot0, + y1, yDot1, + yMidDots, forward); + } else { + interpolator = new DummyStepInterpolator(y, forward); + } + interpolator.storeTime(t0); + + double currentT = t0; + double hNew = 0; + double maxError = Double.MAX_VALUE; + boolean previousRejected = false; + boolean firstTime = true; + boolean newStep = true; + boolean lastStep = false; + boolean firstStepAlreadyComputed = false; + handler.reset(); + costPerTimeUnit[0] = 0; + while (! lastStep) { + + double h; + double error; + boolean reject = false; + + if (newStep) { + + interpolator.shift(); + + // first evaluation, at the beginning of the step + if (! firstStepAlreadyComputed) { + equations.computeDerivatives(currentT, y, yDot0); + } + + if (firstTime) { + + hNew = initializeStep(equations, forward, + 2 * targetIter + 1, scale, + currentT, y, yDot0, yTmp, yTmpDot); + + if (! forward) { + hNew = -hNew; + } + + } + + newStep = false; + + } + + h = hNew; + + // step adjustment near bounds + if ((forward && (currentT + h > t)) + || ((! forward) && (currentT + h < t))) { + h = t - currentT; + } + double nextT = currentT + h; + lastStep = forward ? (nextT >= t) : (nextT <= t); + + // iterate over several substep sizes + int k = -1; + for (boolean loop = true; loop; ) { + + ++k; + + // modified midpoint integration with the current substep + if ( ! tryStep(equations, currentT, y, h, k, scale, fk[k], + (k == 0) ? yMidDots[0] : diagonal[k-1], + (k == 0) ? y1 : y1Diag[k-1], + yTmp)) { + + // the stability check failed, we reduce the global step + hNew = Math.abs(filterStep(h * stabilityReduction, false)); + reject = true; + loop = false; + + } else { + + // the substep was computed successfully + if (k > 0) { + + // extrapolate the state at the end of the step + // using last iteration data + extrapolate(0, k, y1Diag, y1); + rescale(y, y1, scale); + + // estimate the error at the end of the step. + error = 0; + for (int j = 0; j < y0.length; ++j) { + double e = Math.abs(y1[j] - y1Diag[0][j]) / scale[j]; + error += e * e; + } + error = Math.sqrt(error / y0.length); + + if ((error > 1.0e15) || ((k > 1) && (error > maxError))) { + // error is too big, we reduce the global step + hNew = Math.abs(filterStep(h * stabilityReduction, false)); + reject = true; + loop = false; + } else { + + maxError = Math.max(4 * error, 1.0); + + // compute optimal stepsize for this order + double exp = 1.0 / (2 * k + 1); + double fac = stepControl2 / Math.pow(error / stepControl1, exp); + double pow = Math.pow(stepControl3, exp); + fac = Math.max(pow / stepControl4, Math.min(1 / pow, fac)); + optimalStep[k] = Math.abs(filterStep(h * fac, true)); + costPerTimeUnit[k] = costPerStep[k] / optimalStep[k]; + + // check convergence + switch (k - targetIter) { + + case -1 : + if ((targetIter > 1) && ! previousRejected) { + + // check if we can stop iterations now + if (error <= 1.0) { + // convergence have been reached just before targetIter + loop = false; + } else { + // estimate if there is a chance convergence will + // be reached on next iteration, using the + // asymptotic evolution of error + double ratio = sequence [k] * sequence[k+1] + / (sequence[0] * sequence[0]); + if (error > ratio * ratio) { + // we don't expect to converge on next iteration + // we reject the step immediately and reduce order + reject = true; + loop = false; + targetIter = k; + if ((targetIter > 1) + && (costPerTimeUnit[targetIter-1] + < orderControl1 * costPerTimeUnit[targetIter])) { + --targetIter; + } + hNew = optimalStep[targetIter]; + } + } + } + break; + + case 0: + if (error <= 1.0) { + // convergence has been reached exactly at targetIter + loop = false; + } else { + // estimate if there is a chance convergence will + // be reached on next iteration, using the + // asymptotic evolution of error + double ratio = sequence[k+1] / sequence[0]; + if (error > ratio * ratio) { + // we don't expect to converge on next iteration + // we reject the step immediately + reject = true; + loop = false; + if ((targetIter > 1) + && (costPerTimeUnit[targetIter-1] + < orderControl1 * costPerTimeUnit[targetIter])) { + --targetIter; + } + hNew = optimalStep[targetIter]; + } + } + break; + + case 1 : + if (error > 1.0) { + reject = true; + if ((targetIter > 1) + && (costPerTimeUnit[targetIter-1] + < orderControl1 * costPerTimeUnit[targetIter])) { + --targetIter; + } + hNew = optimalStep[targetIter]; + } + loop = false; + break; + + default : + if ((firstTime || lastStep) && (error <= 1.0)) { + loop = false; + } + break; + + } + + } + } + } + } + + // dense output handling + double hInt = getMaxStep(); + if (denseOutput && ! reject) { + + // extrapolate state at middle point of the step + for (int j = 1; j <= k; ++j) { + extrapolate(0, j, diagonal, yMidDots[0]); + } + + // derivative at end of step + equations.computeDerivatives(currentT + h, y1, yDot1); + + int mu = 2 * k - mudif + 3; + + for (int l = 0; l < mu; ++l) { + + // derivative at middle point of the step + int l2 = l / 2; + double factor = Math.pow(0.5 * sequence[l2], l); + int middleIndex = fk[l2].length / 2; + for (int i = 0; i < y0.length; ++i) { + yMidDots[l+1][i] = factor * fk[l2][middleIndex + l][i]; + } + for (int j = 1; j <= k - l2; ++j) { + factor = Math.pow(0.5 * sequence[j + l2], l); + middleIndex = fk[l2+j].length / 2; + for (int i = 0; i < y0.length; ++i) { + diagonal[j-1][i] = factor * fk[l2+j][middleIndex+l][i]; + } + extrapolate(l2, j, diagonal, yMidDots[l+1]); + } + for (int i = 0; i < y0.length; ++i) { + yMidDots[l+1][i] *= h; + } + + // compute centered differences to evaluate next derivatives + for (int j = (l + 1) / 2; j <= k; ++j) { + for (int m = fk[j].length - 1; m >= 2 * (l + 1); --m) { + for (int i = 0; i < y0.length; ++i) { + fk[j][m][i] -= fk[j][m-2][i]; + } + } + } + + } + + if (mu >= 0) { + + // estimate the dense output coefficients + GraggBulirschStoerStepInterpolator gbsInterpolator + = (GraggBulirschStoerStepInterpolator) interpolator; + gbsInterpolator.computeCoefficients(mu, h); + + if (useInterpolationError) { + // use the interpolation error to limit stepsize + double interpError = gbsInterpolator.estimateError(scale); + hInt = Math.abs(h / Math.max(Math.pow(interpError, 1.0 / (mu+4)), + 0.01)); + if (interpError > 10.0) { + hNew = hInt; + reject = true; + } + } + + // Switching functions handling + if (!reject) { + interpolator.storeTime(currentT + h); + if (switchesHandler.evaluateStep(interpolator)) { + reject = true; + hNew = Math.abs(switchesHandler.getEventTime() - currentT); + } + } + + } + + if (!reject) { + // we will reuse the slope for the beginning of next step + firstStepAlreadyComputed = true; + System.arraycopy(yDot1, 0, yDot0, 0, y0.length); + } + + } + + if (! reject) { + + // store end of step state + currentT += h; + System.arraycopy(y1, 0, y, 0, y0.length); + + switchesHandler.stepAccepted(currentT, y); + if (switchesHandler.stop()) { + lastStep = true; + } + + // provide the step data to the step handler + interpolator.storeTime(currentT); + handler.handleStep(interpolator, lastStep); + + switchesHandler.reset(currentT, y); + + int optimalIter; + if (k == 1) { + optimalIter = 2; + if (previousRejected) { + optimalIter = 1; + } + } else if (k <= targetIter) { + optimalIter = k; + if (costPerTimeUnit[k-1] < orderControl1 * costPerTimeUnit[k]) { + optimalIter = k-1; + } else if (costPerTimeUnit[k] < orderControl2 * costPerTimeUnit[k-1]) { + optimalIter = Math.min(k+1, sequence.length - 2); + } + } else { + optimalIter = k - 1; + if ((k > 2) + && (costPerTimeUnit[k-2] < orderControl1 * costPerTimeUnit[k-1])) { + optimalIter = k - 2; + } + if (costPerTimeUnit[k] < orderControl2 * costPerTimeUnit[optimalIter]) { + optimalIter = Math.min(k, sequence.length - 2); + } + } + + if (previousRejected) { + // after a rejected step neither order nor stepsize + // should increase + targetIter = Math.min(optimalIter, k); + hNew = Math.min(Math.abs(h), optimalStep[targetIter]); + } else { + // stepsize control + if (optimalIter <= k) { + hNew = optimalStep[optimalIter]; + } else { + if ((k < targetIter) + && (costPerTimeUnit[k] < orderControl2 * costPerTimeUnit[k-1])) { + hNew = filterStep(optimalStep[k] + * costPerStep[optimalIter+1] / costPerStep[k], + false); + } else { + hNew = filterStep(optimalStep[k] + * costPerStep[optimalIter] / costPerStep[k], + false); + } + } + + targetIter = optimalIter; + + } + + newStep = true; + + } + + hNew = Math.min(hNew, hInt); + if (! forward) { + hNew = -hNew; + } + + firstTime = false; + + if (reject) { + lastStep = false; + previousRejected = true; + } else { + previousRejected = false; + } + + } + + } + + /** maximal order. */ + private int maxOrder; + + /** step size sequence. */ + private int[] sequence; + + /** overall cost of applying step reduction up to iteration k+1, + * in number of calls. + */ + private int[] costPerStep; + + /** cost per unit step. */ + private double[] costPerTimeUnit; + + /** optimal steps for each order. */ + private double[] optimalStep; + + /** extrapolation coefficients. */ + private double[][] coeff; + + /** stability check enabling parameter. */ + private boolean performTest; + + /** maximal number of checks for each iteration. */ + private int maxChecks; + + /** maximal number of iterations for which checks are performed. */ + private int maxIter; + + /** stepsize reduction factor in case of stability check failure. */ + private double stabilityReduction; + + /** first stepsize control factor. */ + private double stepControl1; + + /** second stepsize control factor. */ + private double stepControl2; + + /** third stepsize control factor. */ + private double stepControl3; + + /** fourth stepsize control factor. */ + private double stepControl4; + + /** first order control factor. */ + private double orderControl1; + + /** second order control factor. */ + private double orderControl2; + + /** dense outpute required. */ + private boolean denseOutput; + + /** use interpolation error in stepsize control. */ + private boolean useInterpolationError; + + /** interpolation order control parameter. */ + private int mudif; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/GraggBulirschStoerStepInterpolator.java b/src/mantissa/src/org/spaceroots/mantissa/ode/GraggBulirschStoerStepInterpolator.java new file mode 100644 index 000000000..59468c083 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/GraggBulirschStoerStepInterpolator.java @@ -0,0 +1,404 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.io.IOException; + +/** + * This class implements an interpolator for the Gragg-Bulirsch-Stoer + * integrator. + + *

    This interpolator compute dense output inside the last step + * produced by a Gragg-Bulirsch-Stoer integrator.

    + + *

    + * This implementation is basically a reimplementation in Java of the + * odex + * fortran code by E. Hairer and G. Wanner. The redistribution policy + * for this code is available here, for + * convenience, it is reproduced below.

    + *

    + + * + * + + * + + * + *
    Copyright (c) 2004, Ernst Hairer
    Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + *
      + *
    • Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer.
    • + *
    • Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution.
    • + *
    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    + + * @see GraggBulirschStoerIntegrator + + * @version $Id: GraggBulirschStoerStepInterpolator.java 1705 2006-09-17 19:57:39Z luc $ + * @author E. Hairer and G. Wanner (fortran version) + * @author L. Maisonobe (Java port) + + */ + +class GraggBulirschStoerStepInterpolator + extends AbstractStepInterpolator { + + /** Slope at the beginning of the step. */ + private double[] y0Dot; + + /** State at the end of the step. */ + private double[] y1; + + /** Slope at the end of the step. */ + private double[] y1Dot; + + /** Derivatives at the middle of the step. + * element 0 is state at midpoint, element 1 is first derivative ... + */ + private double[][] yMidDots; + + /** Interpolation polynoms. */ + private double[][] polynoms; + + /** Error coefficients for the interpolation. */ + private double[] errfac; + + /** Degree of the interpolation polynoms. */ + private int currentDegree; + + /** Reallocate the internal tables. + * Reallocate the internal tables in order to be able to handle + * interpolation polynoms up to the given degree + * @param maxDegree maximal degree to handle + */ + private void resetTables(int maxDegree) { + + if (maxDegree < 0) { + polynoms = null; + errfac = null; + currentDegree = -1; + } else { + + double[][] newPols = new double[maxDegree + 1][]; + if (polynoms != null) { + System.arraycopy(polynoms, 0, newPols, 0, polynoms.length); + for (int i = polynoms.length; i < newPols.length; ++i) { + newPols[i] = new double[currentState.length]; + } + } else { + for (int i = 0; i < newPols.length; ++i) { + newPols[i] = new double[currentState.length]; + } + } + polynoms = newPols; + + // initialize the error factors array for interpolation + if (maxDegree <= 4) { + errfac = null; + } else { + errfac = new double[maxDegree - 4]; + for (int i = 0; i < errfac.length; ++i) { + int ip5 = i + 5; + errfac[i] = 1.0 / (ip5 * ip5); + double e = 0.5 * Math.sqrt (((double) (i + 1)) / ip5); + for (int j = 0; j <= i; ++j) { + errfac[i] *= e / (j + 1); + } + } + } + + currentDegree = 0; + + } + + } + + /** Simple constructor. + * This constructor should not be used directly, it is only intended + * for the serialization process. + */ + public GraggBulirschStoerStepInterpolator() { + y0Dot = null; + y1 = null; + y1Dot = null; + yMidDots = null; + resetTables(-1); + } + + /** Simple constructor. + * @param y reference to the integrator array holding the current state + * @param y0Dot reference to the integrator array holding the slope + * at the beginning of the step + * @param y1 reference to the integrator array holding the state at + * the end of the step + * @param y1Dot reference to the integrator array holding the slope + * at theend of the step + * @param yMidDots reference to the integrator array holding the + * derivatives at the middle point of the step + * @param forward integration direction indicator + */ + public GraggBulirschStoerStepInterpolator(double[] y, double[] y0Dot, + double[] y1, double[] y1Dot, + double[][] yMidDots, + boolean forward) { + + super(y, forward); + this.y0Dot = y0Dot; + this.y1 = y1; + this.y1Dot = y1Dot; + this.yMidDots = yMidDots; + + resetTables(yMidDots.length + 4); + + } + + /** Copy constructor. + * @param interpolator interpolator to copy from. The copy is a deep + * copy: its arrays are separated from the original arrays of the + * instance + */ + public GraggBulirschStoerStepInterpolator + (GraggBulirschStoerStepInterpolator interpolator) { + + super(interpolator); + + int dimension = currentState.length; + + // the interpolator has been finalized, + // the following arrays are not needed anymore + y0Dot = null; + y1 = null; + y1Dot = null; + yMidDots = null; + + // copy the interpolation polynoms (up to the current degree only) + if (interpolator.polynoms == null) { + polynoms = null; + currentDegree = -1; + } else { + resetTables(interpolator.currentDegree); + for (int i = 0; i < polynoms.length; ++i) { + polynoms[i] = new double[dimension]; + System.arraycopy(interpolator.polynoms[i], 0, + polynoms[i], 0, dimension); + } + currentDegree = interpolator.currentDegree; + } + + } + + /** + * Clone the instance. + * the copy is a deep copy: its arrays are separated from the + * original arrays of the instance + * @return a copy of the instance + */ + public Object clone() { + return new GraggBulirschStoerStepInterpolator(this); + } + + /** Compute the interpolation coefficients for dense output. + * @param mu degree of the interpolation polynom + * @param h current step + */ + public void computeCoefficients(int mu, double h) { + + if ((polynoms == null) || (polynoms.length <= (mu + 4))) { + resetTables(mu + 4); + } + + currentDegree = mu + 4; + + for (int i = 0; i < currentState.length; ++i) { + + double yp0 = h * y0Dot[i]; + double yp1 = h * y1Dot[i]; + double ydiff = y1[i] - currentState[i]; + double aspl = ydiff - yp1; + double bspl = yp0 - ydiff; + + polynoms[0][i] = currentState[i]; + polynoms[1][i] = ydiff; + polynoms[2][i] = aspl; + polynoms[3][i] = bspl; + + if (mu < 0) { + return; + } + + // compute the remaining coefficients + double ph0 = 0.5 * (currentState[i] + y1[i]) + 0.125 * (aspl + bspl); + polynoms[4][i] = 16 * (yMidDots[0][i] - ph0); + + if (mu > 0) { + double ph1 = ydiff + 0.25 * (aspl - bspl); + polynoms[5][i] = 16 * (yMidDots[1][i] - ph1); + + if (mu > 1) { + double ph2 = yp1 - yp0; + polynoms[6][i] = 16 * (yMidDots[2][i] - ph2 + polynoms[4][i]); + + if (mu > 2) { + double ph3 = 6 * (bspl - aspl); + polynoms[7][i] = 16 * (yMidDots[3][i] - ph3 + 3 * polynoms[5][i]); + + for (int j = 4; j <= mu; ++j) { + double fac1 = 0.5 * j * (j - 1); + double fac2 = 2 * fac1 * (j - 2) * (j - 3); + polynoms[j+4][i] = 16 * (yMidDots[j][i] + + fac1 * polynoms[j+2][i] + - fac2 * polynoms[j][i]); + } + + } + } + } + } + + } + + /** Estimate interpolation error. + * @param scale scaling array + * @return estimate of the interpolation error + */ + public double estimateError(double[] scale) { + double error = 0; + if (currentDegree >= 5) { + for (int i = 0; i < currentState.length; ++i) { + double e = polynoms[currentDegree][i] / scale[i]; + error += e * e; + } + error = Math.sqrt(error / currentState.length) * errfac[currentDegree-5]; + } + return error; + } + + /** Compute the state at the interpolated time. + * This is the main processing method that should be implemented by + * the derived classes to perform the interpolation. + * @param theta normalized interpolation abscissa within the step + * (theta is zero at the previous time step and one at the current time step) + * @param oneMinusThetaH time gap between the interpolated time and + * the current time + * @throws DerivativeException this exception is propagated to the caller if the + * underlying user function triggers one + */ + protected void computeInterpolatedState(double theta, + double oneMinusThetaH) + throws DerivativeException { + + int dimension = currentState.length; + + double oneMinusTheta = 1.0 - theta; + double theta05 = theta - 0.5; + double t4 = theta * oneMinusTheta; + t4 = t4 * t4; + + for (int i = 0; i < dimension; ++i) { + interpolatedState[i] = polynoms[0][i] + + theta * (polynoms[1][i] + + oneMinusTheta * (polynoms[2][i] * theta + + polynoms[3][i] * oneMinusTheta)); + + if (currentDegree > 3) { + double c = polynoms[currentDegree][i]; + for (int j = currentDegree - 1; j > 3; --j) { + c = polynoms[j][i] + c * theta05 / (j - 3); + } + interpolatedState[i] += t4 * c; + } + } + + } + + /** Save the state of the instance. + * @param out stream where to save the state + * @exception IOException in case of write error + */ + public void writeExternal(ObjectOutput out) + throws IOException { + + int dimension = currentState.length; + + // save the state of the base class + writeBaseExternal(out); + + // save the local attributes (but not the temporary vectors) + out.writeInt(currentDegree); + for (int k = 0; k <= currentDegree; ++k) { + for (int l = 0; l < dimension; ++l) { + out.writeDouble(polynoms[k][l]); + } + } + + } + + /** Read the state of the instance. + * @param in stream where to read the state from + * @exception IOException in case of read error + */ + public void readExternal(ObjectInput in) + throws IOException { + + // read the base class + double t = readBaseExternal(in); + int dimension = currentState.length; + + // read the local attributes + int degree = in.readInt(); + resetTables(degree); + currentDegree = degree; + + for (int k = 0; k <= currentDegree; ++k) { + for (int l = 0; l < dimension; ++l) { + polynoms[k][l] = in.readDouble(); + } + } + + try { + // we can now set the interpolated time and state + setInterpolatedTime(t); + } catch (DerivativeException e) { + IOException ioe = new IOException(); + ioe.initCause(e); + throw ioe; + } + + } + + private static final long serialVersionUID = 7320613236731409847L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/HighamHall54Integrator.java b/src/mantissa/src/org/spaceroots/mantissa/ode/HighamHall54Integrator.java new file mode 100644 index 000000000..3a794c659 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/HighamHall54Integrator.java @@ -0,0 +1,139 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +/** + * This class implements the 5(4) Higham and Hall integrator for + * Ordinary Differential Equations. + + *

    This integrator is an embedded Runge-Kutta-Fehlberg integrator + * of order 5(4) used in local extrapolation mode (i.e. the solution + * is computed using the high order formula) with stepsize control + * (and automatic step initialization) and continuous output. This + * method uses 7 functions evaluations per step.

    + + * @version $Id: HighamHall54Integrator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class HighamHall54Integrator + extends RungeKuttaFehlbergIntegrator { + + private static final String methodName = new String("Higham-Hall 5(4)"); + + private static final double[] c = { + 2.0/9.0, 1.0/3.0, 1.0/2.0, 3.0/5.0, 1.0, 1.0 + }; + + private static final double[][] a = { + {2.0/9.0}, + {1.0/12.0, 1.0/4.0}, + {1.0/8.0, 0.0, 3.0/8.0}, + {91.0/500.0, -27.0/100.0, 78.0/125.0, 8.0/125.0}, + {-11.0/20.0, 27.0/20.0, 12.0/5.0, -36.0/5.0, 5.0}, + {1.0/12.0, 0.0, 27.0/32.0, -4.0/3.0, 125.0/96.0, 5.0/48.0} + }; + + private static final double[] b = { + 1.0/12.0, 0.0, 27.0/32.0, -4.0/3.0, 125.0/96.0, 5.0/48.0, 0.0 + }; + + private static final double[] e = { + -1.0/20.0, 0.0, 81.0/160.0, -6.0/5.0, 25.0/32.0, 1.0/16.0, -1.0/10.0 + }; + + /** Simple constructor. + * Build a fifth order Higham and Hall integrator with the given step bounds + * @param minStep minimal step (must be positive even for backward + * integration), the last step can be smaller than this + * @param maxStep maximal step (must be positive even for backward + * integration) + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + */ + public HighamHall54Integrator(double minStep, double maxStep, + double scalAbsoluteTolerance, + double scalRelativeTolerance) { + super(false, c, a, b, new HighamHall54StepInterpolator(), + minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance); + } + + /** Simple constructor. + * Build a fifth order Higham and Hall integrator with the given step bounds + * @param minStep minimal step (must be positive even for backward + * integration), the last step can be smaller than this + * @param maxStep maximal step (must be positive even for backward + * integration) + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + */ + public HighamHall54Integrator(double minStep, double maxStep, + double[] vecAbsoluteTolerance, + double[] vecRelativeTolerance) { + super(false, c, a, b, new HighamHall54StepInterpolator(), + minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance); + } + + /** Get the name of the method. + * @return name of the method + */ + public String getName() { + return methodName; + } + + /** Get the order of the method. + * @return order of the method + */ + public int getOrder() { + return 5; + } + + /** Compute the error ratio. + * @param yDotK derivatives computed during the first stages + * @param y0 estimate of the step at the start of the step + * @param y1 estimate of the step at the end of the step + * @param h current step + * @return error ratio, greater than 1 if step should be rejected + */ + protected double estimateError(double[][] yDotK, + double[] y0, double[] y1, + double h) { + + double error = 0; + + for (int j = 0; j < y0.length; ++j) { + double errSum = e[0] * yDotK[0][j]; + for (int l = 1; l < e.length; ++l) { + errSum += e[l] * yDotK[l][j]; + } + + double yScale = Math.max(Math.abs(y0[j]), Math.abs(y1[j])); + double tol = (vecAbsoluteTolerance == null) + ? (scalAbsoluteTolerance + scalRelativeTolerance * yScale) + : (vecAbsoluteTolerance[j] + vecRelativeTolerance[j] * yScale); + double ratio = h * errSum / tol; + error += ratio * ratio; + + } + + return Math.sqrt(error / y0.length); + + } + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/HighamHall54StepInterpolator.java b/src/mantissa/src/org/spaceroots/mantissa/ode/HighamHall54StepInterpolator.java new file mode 100644 index 000000000..dc8dedf65 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/HighamHall54StepInterpolator.java @@ -0,0 +1,96 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +/** + * This class represents an interpolator over the last step during an + * ODE integration for the 5(4) Higham and Hall integrator. + * + * @see HighamHall54Integrator + * + * @version $Id: HighamHall54StepInterpolator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + * + */ + +class HighamHall54StepInterpolator + extends RungeKuttaStepInterpolator { + + /** Simple constructor. + * This constructor builds an instance that is not usable yet, the + * {@link AbstractStepInterpolator#reinitialize} method should be called + * before using the instance in order to initialize the internal arrays. This + * constructor is used only in order to delay the initialization in + * some cases. The {@link RungeKuttaFehlbergIntegrator} uses the + * prototyping design pattern to create the step interpolators by + * cloning an uninitialized model and latter initializing the copy. + */ + public HighamHall54StepInterpolator() { + super(); + } + + /** Copy constructor. + * @param interpolator interpolator to copy from. The copy is a deep + * copy: its arrays are separated from the original arrays of the + * instance + */ + public HighamHall54StepInterpolator(HighamHall54StepInterpolator interpolator) { + super(interpolator); + } + + /** + * Clone the instance. + * the copy is a deep copy: its arrays are separated from the + * original arrays of the instance + * @return a copy of the instance + */ + public Object clone() { + return new HighamHall54StepInterpolator(this); + } + + /** Compute the state at the interpolated time. + * @param theta normalized interpolation abscissa within the step + * (theta is zero at the previous time step and one at the current time step) + * @param oneMinusThetaH time gap between the interpolated time and + * the current time + * @throws DerivativeException this exception is propagated to the caller if the + * underlying user function triggers one + */ + protected void computeInterpolatedState(double theta, + double oneMinusThetaH) + throws DerivativeException { + + double theta2 = theta * theta; + + double b0 = h * (-1.0/12.0 + theta * (1.0 + theta * (-15.0/4.0 + theta * (16.0/3.0 + theta * -5.0/2.0)))); + double b2 = h * (-27.0/32.0 + theta2 * (459.0/32.0 + theta * (-243.0/8.0 + theta * 135.0/8.0))); + double b3 = h * (4.0/3.0 + theta2 * (-22.0 + theta * (152.0/3.0 + theta * -30.0))); + double b4 = h * (-125.0/96.0 + theta2 * (375.0/32.0 + theta * (-625.0/24.0 + theta * 125.0/8.0))); + double b5 = h * (-5.0/48.0 + theta2 * (-5.0/16.0 + theta * 5.0/12.0)); + + for (int i = 0; i < interpolatedState.length; ++i) { + interpolatedState[i] = currentState[i] + + b0 * yDotK[0][i] + b2 * yDotK[2][i] + b3 * yDotK[3][i] + + b4 * yDotK[4][i] + b5 * yDotK[5][i]; + } + + } + + private static final long serialVersionUID = -3583240427587318654L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/IntegratorException.java b/src/mantissa/src/org/spaceroots/mantissa/ode/IntegratorException.java new file mode 100644 index 000000000..381c0480a --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/IntegratorException.java @@ -0,0 +1,42 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +import org.spaceroots.mantissa.MantissaException; + +/** + * This exception is made available to users to report + * the error conditions that are triggered during integration + * @author Luc Maisonobe + * @version $Id: IntegratorException.java 1705 2006-09-17 19:57:39Z luc $ + */ +public class IntegratorException + extends MantissaException { + + /** Simple constructor. + * Build an exception by translating and formating a message + * @param specifier format specifier (to be translated) + * @param parts to insert in the format (no translation) + */ + public IntegratorException(String specifier, String[] parts) { + super(specifier, parts); + } + + private static final long serialVersionUID = -1390328069787882608L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/MidpointIntegrator.java b/src/mantissa/src/org/spaceroots/mantissa/ode/MidpointIntegrator.java new file mode 100644 index 000000000..0efce5ed5 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/MidpointIntegrator.java @@ -0,0 +1,75 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +/** + * This class implements a second order Runge-Kutta integrator for + * Ordinary Differential Equations. + + *

    This method is an explicit Runge-Kutta method, its Butcher-array + * is the following one : + *

    + *    0  |  0    0
    + *   1/2 | 1/2   0
    + *       |----------
    + *       |  0    1
    + * 
    + *

    + + * @see EulerIntegrator + * @see ClassicalRungeKuttaIntegrator + * @see GillIntegrator + + * @version $Id: MidpointIntegrator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class MidpointIntegrator + extends RungeKuttaIntegrator { + + private static final String methodName = new String("midpoint"); + + private static final double[] c = { + 1.0 / 2.0 + }; + + private static final double[][] a = { + { 1.0 / 2.0 } + }; + + private static final double[] b = { + 0.0, 1.0 + }; + + /** Simple constructor. + * Build a midpoint integrator with the given step. + * @param step integration step + */ + public MidpointIntegrator(double step) { + super(false, c, a, b, new MidpointStepInterpolator(), step); + } + + /** Get the name of the method. + * @return name of the method + */ + public String getName() { + return methodName; + } + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/MidpointStepInterpolator.java b/src/mantissa/src/org/spaceroots/mantissa/ode/MidpointStepInterpolator.java new file mode 100644 index 000000000..fabadae02 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/MidpointStepInterpolator.java @@ -0,0 +1,103 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +/** + * This class implements a step interpolator for second order + * Runge-Kutta integrator. + + *

    This interpolator allow to compute dense output inside the last + * step computed. The interpolation equation is consistent with the + * integration scheme : + + *

    + *   y(t_n + theta h) = y (t_n + h) + (1-theta) h [theta y'_1 - (1+theta) y'_2]
    + * 
    + + * where theta belongs to [0 ; 1] and where y'_1 and y'_2 are the two + * evaluations of the derivatives already computed during the + * step.

    + + * @see MidpointIntegrator + + * @version $Id: MidpointStepInterpolator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +class MidpointStepInterpolator + extends RungeKuttaStepInterpolator { + + /** Simple constructor. + * This constructor builds an instance that is not usable yet, the + * {@link AbstractStepInterpolator#reinitialize} method should be called + * before using the instance in order to initialize the internal arrays. This + * constructor is used only in order to delay the initialization in + * some cases. The {@link RungeKuttaIntegrator} class uses the + * prototyping design pattern to create the step interpolators by + * cloning an uninitialized model and latter initializing the copy. + */ + public MidpointStepInterpolator() { + } + + /** Copy constructor. + * @param interpolator interpolator to copy from. The copy is a deep + * copy: its arrays are separated from the original arrays of the + * instance + */ + public MidpointStepInterpolator(MidpointStepInterpolator interpolator) { + super(interpolator); + } + + /** + * Clone the instance. + * the copy is a deep copy: its arrays are separated from the + * original arrays of the instance + * @return a copy of the instance + */ + public Object clone() { + return new MidpointStepInterpolator(this); + } + + /** Compute the state at the interpolated time. + * This is the main processing method that should be implemented by + * the derived classes to perform the interpolation. + * @param theta normalized interpolation abscissa within the step + * (theta is zero at the previous time step and one at the current time step) + * @param oneMinusThetaH time gap between the interpolated time and + * the current time + * @throws DerivativeException this exception is propagated to the caller if the + * underlying user function triggers one + */ + protected void computeInterpolatedState(double theta, + double oneMinusThetaH) + throws DerivativeException { + + double coeff1 = oneMinusThetaH * theta; + double coeff2 = oneMinusThetaH * (1.0 + theta); + + for (int i = 0; i < interpolatedState.length; ++i) { + interpolatedState[i] = currentState[i] + + coeff1 * yDotK[0][i] - coeff2 * yDotK[1][i]; + } + + } + + private static final long serialVersionUID = -865524111506042509L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/RungeKuttaFehlbergIntegrator.java b/src/mantissa/src/org/spaceroots/mantissa/ode/RungeKuttaFehlbergIntegrator.java new file mode 100644 index 000000000..e27bbb824 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/RungeKuttaFehlbergIntegrator.java @@ -0,0 +1,397 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +/** + * This class implements the common part of all Runge-Kutta-Fehlberg + * integrators for Ordinary Differential Equations. + + *

    These methods are embedded explicit Runge-Kutta methods with two + * sets of coefficients allowing to estimate the error, their Butcher + * arrays are as follows : + *

    + *    0  |
    + *   c2  | a21
    + *   c3  | a31  a32
    + *   ... |        ...
    + *   cs  | as1  as2  ...  ass-1
    + *       |--------------------------
    + *       |  b1   b2  ...   bs-1  bs
    + *       |  b'1  b'2 ...   b's-1 b's
    + * 
    + *

    + + *

    In fact, we rather use the array defined by ej = bj - b'j to + * compute directly the error rather than computing two estimates and + * then comparing them.

    + + *

    Some methods are qualified as fsal (first same as last) + * methods. This means the last evaluation of the derivatives in one + * step is the same as the first in the next step. Then, this + * evaluation can be reused from one step to the next one and the cost + * of such a method is really s-1 evaluations despite the method still + * has s stages. This behaviour is true only for successful steps, if + * the step is rejected after the error estimation phase, no + * evaluation is saved. For an fsal method, we have cs = 1 and + * asi = bi for all i.

    + + * @version $Id: RungeKuttaFehlbergIntegrator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public abstract class RungeKuttaFehlbergIntegrator + extends AdaptiveStepsizeIntegrator { + + /** Build a Runge-Kutta integrator with the given Butcher array. + * @param fsal indicate that the method is an fsal + * @param c time steps from Butcher array (without the first zero) + * @param a internal weights from Butcher array (without the first empty row) + * @param b external weights for the high order method from Butcher array + * @param prototype prototype of the step interpolator to use + * @param minStep minimal step (must be positive even for backward + * integration), the last step can be smaller than this + * @param maxStep maximal step (must be positive even for backward + * integration) + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + */ + protected RungeKuttaFehlbergIntegrator(boolean fsal, + double[] c, double[][] a, double[] b, + RungeKuttaStepInterpolator prototype, + double minStep, double maxStep, + double scalAbsoluteTolerance, + double scalRelativeTolerance) { + + super(minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance); + + this.fsal = fsal; + this.c = c; + this.a = a; + this.b = b; + this.prototype = prototype; + + exp = -1.0 / getOrder(); + + this.safety = 0.9; + + // set the default values of the algorithm control parameters + setMinReduction(0.2); + setMaxGrowth(10.0); + + } + + /** Build a Runge-Kutta integrator with the given Butcher array. + * @param fsal indicate that the method is an fsal + * @param c time steps from Butcher array (without the first zero) + * @param a internal weights from Butcher array (without the first empty row) + * @param b external weights for the high order method from Butcher array + * @param prototype prototype of the step interpolator to use + * @param minStep minimal step (must be positive even for backward + * integration), the last step can be smaller than this + * @param maxStep maximal step (must be positive even for backward + * integration) + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + */ + protected RungeKuttaFehlbergIntegrator(boolean fsal, + double[] c, double[][] a, double[] b, + RungeKuttaStepInterpolator prototype, + double minStep, double maxStep, + double[] vecAbsoluteTolerance, + double[] vecRelativeTolerance) { + + super(minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance); + + this.fsal = fsal; + this.c = c; + this.a = a; + this.b = b; + this.prototype = prototype; + + exp = -1.0 / getOrder(); + + this.safety = 0.9; + + // set the default values of the algorithm control parameters + setMinReduction(0.2); + setMaxGrowth(10.0); + + } + + /** Get the name of the method. + * @return name of the method + */ + public abstract String getName(); + + /** Get the order of the method. + * @return order of the method + */ + public abstract int getOrder(); + + /** Get the safety factor for stepsize control. + * @return safety factor + */ + public double getSafety() { + return safety; + } + + /** Set the safety factor for stepsize control. + * @param safety safety factor + */ + public void setSafety(double safety) { + this.safety = safety; + } + + public void integrate(FirstOrderDifferentialEquations equations, + double t0, double[] y0, + double t, double[] y) + throws DerivativeException, IntegratorException { + + // sanity check + if (equations.getDimension() != y0.length) { + throw new IntegratorException("dimensions mismatch: ODE problem has dimension {0}," + + " state vector has dimension {1}", + new String[] { + Integer.toString(equations.getDimension()), + Integer.toString(y0.length) + }); + } + if (Math.abs(t - t0) <= 1.0e-12 * Math.max(Math.abs(t0), Math.abs(t))) { + throw new IntegratorException("too small integration interval: length = {0}", + new String[] { + Double.toString(Math.abs(t - t0)) + }); + } + + boolean forward = (t > t0); + + // create some internal working arrays + int stages = c.length + 1; + if (y != y0) { + System.arraycopy(y0, 0, y, 0, y0.length); + } + double[][] yDotK = new double[stages][]; + for (int i = 0; i < stages; ++i) { + yDotK [i] = new double[y0.length]; + } + double[] yTmp = new double[y0.length]; + + // set up an interpolator sharing the integrator arrays + AbstractStepInterpolator interpolator; + if (handler.requiresDenseOutput() || (! switchesHandler.isEmpty())) { + RungeKuttaStepInterpolator rki = (RungeKuttaStepInterpolator) prototype.clone(); + rki.reinitialize(equations, yTmp, yDotK, forward); + interpolator = rki; + } else { + interpolator = new DummyStepInterpolator(yTmp, forward); + } + interpolator.storeTime(t0); + + double currentT = t0; + double hNew = 0; + boolean firstTime = true; + boolean lastStep; + handler.reset(); + do { + + interpolator.shift(); + + double h = 0; + double error = 0; + for (boolean loop = true; loop;) { + + if (firstTime || !fsal) { + // first stage + equations.computeDerivatives(currentT, y, yDotK[0]); + } + + if (firstTime) { + double[] scale; + if (vecAbsoluteTolerance != null) { + scale = vecAbsoluteTolerance; + } else { + scale = new double[y0.length]; + for (int i = 0; i < scale.length; ++i) { + scale[i] = scalAbsoluteTolerance; + } + } + hNew = initializeStep(equations, forward, getOrder(), scale, + currentT, y, yDotK[0], yTmp, yDotK[1]); + firstTime = false; + } + + h = hNew; + + // step adjustment near bounds + if ((forward && (currentT + h > t)) + || ((! forward) && (currentT + h < t))) { + h = t - currentT; + } + + // next stages + for (int k = 1; k < stages; ++k) { + + for (int j = 0; j < y0.length; ++j) { + double sum = a[k-1][0] * yDotK[0][j]; + for (int l = 1; l < k; ++l) { + sum += a[k-1][l] * yDotK[l][j]; + } + yTmp[j] = y[j] + h * sum; + } + + equations.computeDerivatives(currentT + c[k-1] * h, yTmp, yDotK[k]); + + } + + // estimate the state at the end of the step + for (int j = 0; j < y0.length; ++j) { + double sum = b[0] * yDotK[0][j]; + for (int l = 1; l < stages; ++l) { + sum += b[l] * yDotK[l][j]; + } + yTmp[j] = y[j] + h * sum; + } + + // estimate the error at the end of the step + error = estimateError(yDotK, y, yTmp, h); + if (error <= 1.0) { + + // Switching functions handling + interpolator.storeTime(currentT + h); + if (switchesHandler.evaluateStep(interpolator)) { + // reject the step to match exactly the next switch time + hNew = switchesHandler.getEventTime() - currentT; + } else { + // accept the step + loop = false; + } + + } else { + // reject the step and attempt to reduce error by stepsize control + double factor = Math.min(maxGrowth, + Math.max(minReduction, + safety * Math.pow(error, exp))); + hNew = filterStep(h * factor, false); + } + + } + + // the step has been accepted + currentT += h; + System.arraycopy(yTmp, 0, y, 0, y0.length); + switchesHandler.stepAccepted(currentT, y); + if (switchesHandler.stop()) { + lastStep = true; + } else { + lastStep = forward ? (currentT >= t) : (currentT <= t); + } + + // provide the step data to the step handler + interpolator.storeTime(currentT); + handler.handleStep(interpolator, lastStep); + + if (fsal) { + // save the last evaluation for the next step + System.arraycopy(yDotK[stages - 1], 0, yDotK[0], 0, y0.length); + } + + switchesHandler.reset(currentT, y); + + if (! lastStep) { + // stepsize control for next step + double factor = Math.min(maxGrowth, + Math.max(minReduction, + safety * Math.pow(error, exp))); + double scaledH = h * factor; + double nextT = currentT + scaledH; + boolean nextIsLast = forward ? (nextT >= t) : (nextT <= t); + hNew = filterStep(scaledH, nextIsLast); + } + + } while (! lastStep); + + } + + /** Get the minimal reduction factor for stepsize control. + * @return minimal reduction factor + */ + public double getMinReduction() { + return minReduction; + } + + /** Set the minimal reduction factor for stepsize control. + * @param minReduction minimal reduction factor + */ + public void setMinReduction(double minReduction) { + this.minReduction = minReduction; + } + + /** Get the maximal growth factor for stepsize control. + * @return maximal growth factor + */ + public double getMaxGrowth() { + return maxGrowth; + } + + /** Set the maximal growth factor for stepsize control. + * @param maxGrowth maximal growth factor + */ + public void setMaxGrowth(double maxGrowth) { + this.maxGrowth = maxGrowth; + } + + /** Compute the error ratio. + * @param yDotK derivatives computed during the first stages + * @param y0 estimate of the step at the start of the step + * @param y1 estimate of the step at the end of the step + * @param h current step + * @return error ratio, greater than 1 if step should be rejected + */ + protected abstract double estimateError(double[][] yDotK, + double[] y0, double[] y1, + double h); + + /** Indicator for fsal methods. */ + private boolean fsal; + + /** Time steps from Butcher array (without the first zero). */ + private double[] c; + + /** Internal weights from Butcher array (without the first empty row). */ + private double[][] a; + + /** External weights for the high order method from Butcher array. */ + private double[] b; + + /** Prototype of the step interpolator. */ + private RungeKuttaStepInterpolator prototype; + + /** Stepsize control exponent. */ + private double exp; + + /** Safety factor for stepsize control. */ + private double safety; + + /** Minimal reduction factor for stepsize control. */ + private double minReduction; + + /** Maximal growth factor for stepsize control. */ + private double maxGrowth; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/RungeKuttaIntegrator.java b/src/mantissa/src/org/spaceroots/mantissa/ode/RungeKuttaIntegrator.java new file mode 100644 index 000000000..1705f6b66 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/RungeKuttaIntegrator.java @@ -0,0 +1,275 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +/** + * This class implements the common part of all fixed step Runge-Kutta + * integrators for Ordinary Differential Equations. + + *

    These methods are explicit Runge-Kutta methods, their Butcher + * arrays are as follows : + *

    + *    0  |
    + *   c2  | a21
    + *   c3  | a31  a32
    + *   ... |        ...
    + *   cs  | as1  as2  ...  ass-1
    + *       |--------------------------
    + *       |  b1   b2  ...   bs-1  bs
    + * 
    + *

    + + *

    Some methods are qualified as fsal (first same as last) + * methods. This means the last evaluation of the derivatives in one + * step is the same as the first in the next step. Then, this + * evaluation can be reused from one step to the next one and the cost + * of such a method is really s-1 evaluations despite the method still + * has s stages. This behaviour is true only for successful steps, if + * the step is rejected after the error estimation phase, no + * evaluation is saved. For an fsal method, we have cs = 1 and + * asi = bi for all i.

    + + * @see EulerIntegrator + * @see ClassicalRungeKuttaIntegrator + * @see GillIntegrator + * @see MidpointIntegrator + + * @version $Id: RungeKuttaIntegrator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public abstract class RungeKuttaIntegrator + implements FirstOrderIntegrator { + + /** Simple constructor. + * Build a Runge-Kutta integrator with the given + * step. The default step handler does nothing. + * @param fsal indicate that the method is an fsal + * @param c time steps from Butcher array (without the first zero) + * @param a internal weights from Butcher array (without the first empty row) + * @param b external weights for the high order method from Butcher array + * @param prototype prototype of the step interpolator to use + * @param step integration step + */ + protected RungeKuttaIntegrator(boolean fsal, + double[] c, double[][] a, double[] b, + RungeKuttaStepInterpolator prototype, + double step) { + this.fsal = fsal; + this.c = c; + this.a = a; + this.b = b; + this.prototype = prototype; + this.step = step; + handler = DummyStepHandler.getInstance(); + switchesHandler = new SwitchingFunctionsHandler(); + } + + /** Get the name of the method. + * @return name of the method + */ + public abstract String getName(); + + /** Set the step handler for this integrator. + * The handler will be called by the integrator for each accepted + * step. + * @param handler handler for the accepted steps + */ + public void setStepHandler (StepHandler handler) { + this.handler = handler; + } + + /** Get the step handler for this integrator. + * @return the step handler for this integrator + */ + public StepHandler getStepHandler() { + return handler; + } + + /** Add a switching function to the integrator. + * @param function switching function + * @param maxCheckInterval maximal time interval between switching + * function checks (this interval prevents missing sign changes in + * case the integration steps becomes very large) + * @param convergence convergence threshold in the event time search + */ + public void addSwitchingFunction(SwitchingFunction function, + double maxCheckInterval, + double convergence) { + switchesHandler.add(function, maxCheckInterval, convergence); + } + + public void integrate(FirstOrderDifferentialEquations equations, + double t0, double[] y0, + double t, double[] y) + throws DerivativeException, IntegratorException { + + // sanity check + if (equations.getDimension() != y0.length) { + throw new IntegratorException("dimensions mismatch: ODE problem has dimension {0}," + + " state vector has dimension {1}", + new String[] { + Integer.toString(equations.getDimension()), + Integer.toString(y0.length) + }); + } + if (Math.abs(t - t0) <= 1.0e-12 * Math.max(Math.abs(t0), Math.abs(t))) { + throw new IntegratorException("too small integration interval: length = {0}", + new String[] { + Double.toString(Math.abs(t - t0)) + }); + } + + boolean forward = (t > t0); + + // create some internal working arrays + int stages = c.length + 1; + if (y != y0) { + System.arraycopy(y0, 0, y, 0, y0.length); + } + double[][] yDotK = new double[stages][]; + for (int i = 0; i < stages; ++i) { + yDotK [i] = new double[y0.length]; + } + double[] yTmp = new double[y0.length]; + + // set up an interpolator sharing the integrator arrays + AbstractStepInterpolator interpolator; + if (handler.requiresDenseOutput() || (! switchesHandler.isEmpty())) { + RungeKuttaStepInterpolator rki = (RungeKuttaStepInterpolator) prototype.clone(); + rki.reinitialize(equations, yTmp, yDotK, forward); + interpolator = rki; + } else { + interpolator = new DummyStepInterpolator(yTmp, forward); + } + interpolator.storeTime(t0); + + // recompute the step + double currentT = t0; + long nbStep = Math.max(1l, Math.abs(Math.round((t - t0) / step))); + double h = (t - t0) / nbStep; + boolean firstTime = true; + boolean lastStep = false; + handler.reset(); + for (long i = 0; ! lastStep; ++i) { + + interpolator.shift(); + + boolean needUpdate = false; + for (boolean loop = true; loop;) { + + if (firstTime || !fsal) { + // first stage + equations.computeDerivatives(currentT, y, yDotK[0]); + firstTime = false; + } + + // next stages + for (int k = 1; k < stages; ++k) { + + for (int j = 0; j < y0.length; ++j) { + double sum = a[k-1][0] * yDotK[0][j]; + for (int l = 1; l < k; ++l) { + sum += a[k-1][l] * yDotK[l][j]; + } + yTmp[j] = y[j] + h * sum; + } + + equations.computeDerivatives(currentT + c[k-1] * h, yTmp, yDotK[k]); + + } + + // estimate the state at the end of the step + for (int j = 0; j < y0.length; ++j) { + double sum = b[0] * yDotK[0][j]; + for (int l = 1; l < stages; ++l) { + sum += b[l] * yDotK[l][j]; + } + yTmp[j] = y[j] + h * sum; + } + + // Switching functions handling + interpolator.storeTime(currentT + h); + if (switchesHandler.evaluateStep(interpolator)) { + needUpdate = true; + h = switchesHandler.getEventTime() - currentT; + } else { + loop = false; + } + + } + + // the step has been accepted + currentT += h; + System.arraycopy(yTmp, 0, y, 0, y0.length); + switchesHandler.stepAccepted(currentT, y); + if (switchesHandler.stop()) { + lastStep = true; + } else { + lastStep = (i == (nbStep - 1)); + } + + // provide the step data to the step handler + interpolator.storeTime(currentT); + handler.handleStep(interpolator, lastStep); + + if (fsal) { + // save the last evaluation for the next step + System.arraycopy(yDotK[stages - 1], 0, yDotK[0], 0, y0.length); + } + + switchesHandler.reset(currentT, y); + + if (needUpdate) { + // a switching function has changed the step + // we need to recompute stepsize + nbStep = Math.max(1l, Math.abs(Math.round((t - currentT) / step))); + h = (t - currentT) / nbStep; + i = -1; + } + + } + + } + + /** Indicator for fsal methods. */ + private boolean fsal; + + /** Time steps from Butcher array (without the first zero). */ + private double[] c; + + /** Internal weights from Butcher array (without the first empty row). */ + private double[][] a; + + /** External weights for the high order method from Butcher array. */ + private double[] b; + + /** Prototype of the step interpolator. */ + private RungeKuttaStepInterpolator prototype; + + /** Integration step. */ + private double step; + + /** Step handler. */ + private StepHandler handler; + + /** Switching functions handler. */ + protected SwitchingFunctionsHandler switchesHandler; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/RungeKuttaStepInterpolator.java b/src/mantissa/src/org/spaceroots/mantissa/ode/RungeKuttaStepInterpolator.java new file mode 100644 index 000000000..9378dd591 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/RungeKuttaStepInterpolator.java @@ -0,0 +1,185 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.io.IOException; + +/** This class represents an interpolator over the last step during an + * ODE integration for Runge-Kutta and Runge-Kutta-Fehlberg + * integrators. + * + * @see RungeKuttaIntegrator + * @see RungeKuttaFehlbergIntegrator + * + * @version $Id: RungeKuttaStepInterpolator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + * + */ + +abstract class RungeKuttaStepInterpolator + extends AbstractStepInterpolator { + + /** Simple constructor. + * This constructor builds an instance that is not usable yet, the + * {@link #reinitialize} method should be called before using the + * instance in order to initialize the internal arrays. This + * constructor is used only in order to delay the initialization in + * some cases. The {@link RungeKuttaIntegrator} and {@link + * RungeKuttaFehlbergIntegrator} classes uses the prototyping design + * pattern to create the step interpolators by cloning an + * uninitialized model and latter initializing the copy. + */ + protected RungeKuttaStepInterpolator() { + super(); + yDotK = null; + equations = null; + } + + /** Copy constructor. + + *

    The copied interpolator should have been finalized before the + * copy, otherwise the copy will not be able to perform correctly any + * interpolation and will throw a {@link NullPointerException} + * later. Since we don't want this constructor to throw the + * exceptions finalization may involve and since we don't want this + * method to modify the state of the copied interpolator, + * finalization is not done automatically, it + * remains under user control.

    + + *

    The copy is a deep copy: its arrays are separated from the + * original arrays of the instance.

    + + * @param interpolator interpolator to copy from. + + */ + public RungeKuttaStepInterpolator(RungeKuttaStepInterpolator interpolator) { + + super(interpolator); + + if (interpolator.currentState != null) { + int dimension = currentState.length; + + yDotK = new double[interpolator.yDotK.length][]; + for (int k = 0; k < interpolator.yDotK.length; ++k) { + yDotK[k] = new double[dimension]; + System.arraycopy(interpolator.yDotK[k], 0, + yDotK[k], 0, dimension); + } + + } else { + yDotK = null; + } + + // we cannot keep any reference to the equations in the copy + // the interpolator should have been finalized before + equations = null; + + } + + /** Reinitialize the instance + *

    Some Runge-Kutta integrators need fewer functions evaluations + * than their counterpart step interpolators. So the interpolator + * should perform the last evaluations they need by themselves. The + * {@link RungeKuttaIntegrator RungeKuttaIntegrator} and {@link + * RungeKuttaFehlbergIntegrator RungeKuttaFehlbergIntegrator} + * abstract classes call this method in order to let the step + * interpolator perform the evaluations it needs. These evaluations + * will be performed during the call to doFinalize if + * any, i.e. only if the step handler either calls the {@link + * AbstractStepInterpolator#finalizeStep finalizeStep} method or the + * {@link AbstractStepInterpolator#getInterpolatedState + * getInterpolatedState} method (for an interpolator which needs a + * finalization) or if it clones the step interpolator.

    + * @param equations set of differential equations being integrated + * @param y reference to the integrator array holding the state at + * the end of the step + * @param yDotK reference to the integrator array holding all the + * intermediate slopes + * @param forward integration direction indicator + */ + public void reinitialize(FirstOrderDifferentialEquations equations, + double[] y, double[][] yDotK, boolean forward) { + reinitialize(y, forward); + this.yDotK = yDotK; + this.equations = equations; + } + + /** Save the state of the instance. + * @param out stream where to save the state + * @exception IOException in case of write error + */ + public void writeExternal(ObjectOutput out) + throws IOException { + + // save the state of the base class + writeBaseExternal(out); + + // save the local attributes + out.writeInt(yDotK.length); + for (int k = 0; k < yDotK.length; ++k) { + for (int i = 0; i < currentState.length; ++i) { + out.writeDouble(yDotK[k][i]); + } + } + + // we do not save any reference to the equations + + } + + /** Read the state of the instance. + * @param in stream where to read the state from + * @exception IOException in case of read error + */ + public void readExternal(ObjectInput in) + throws IOException { + + // read the base class + double t = readBaseExternal(in); + + // read the local attributes + int kMax = in.readInt(); + yDotK = new double[kMax][]; + for (int k = 0; k < kMax; ++k) { + yDotK[k] = new double[currentState.length]; + for (int i = 0; i < currentState.length; ++i) { + yDotK[k][i] = in.readDouble(); + } + } + + equations = null; + + try { + // we can now set the interpolated time and state + setInterpolatedTime(t); + } catch (DerivativeException e) { + IOException ioe = new IOException(); + ioe.initCause(e); + throw ioe; + } + + } + + /** Slopes at the intermediate points */ + protected double[][] yDotK; + + /** Reference to the differential equations beeing integrated. */ + protected FirstOrderDifferentialEquations equations; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/SecondOrderDifferentialEquations.java b/src/mantissa/src/org/spaceroots/mantissa/ode/SecondOrderDifferentialEquations.java new file mode 100644 index 000000000..5f27577ff --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/SecondOrderDifferentialEquations.java @@ -0,0 +1,71 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +/** This interface represents a second order differential equations set. + + *

    This interface should be implemented by all real second order + * differential equation problems before they can be handled by the + * integrators {@link SecondOrderIntegrator#integrate} method.

    + + *

    A second order differential equations problem, as seen by an + * integrator is the second time derivative d2Y/dt^2 of a + * state vector Y, both being one dimensional + * arrays. From the integrator point of view, this derivative depends + * only on the current time t, on the state vector + * Y and on the first time derivative of the state + * vector.

    + + *

    For real problems, the derivative depends also on parameters + * that do not belong to the state vector (dynamical model constants + * for example). These constants are completely outside of the scope + * of this interface, the classes that implement it are allowed to + * handle them as they want.

    + + * @see SecondOrderIntegrator + * @see FirstOrderConverter + * @see FirstOrderDifferentialEquations + * @see org.spaceroots.mantissa.utilities.ArraySliceMappable + + * @version $Id: SecondOrderDifferentialEquations.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public interface SecondOrderDifferentialEquations { + + /** Get the dimension of the problem. + * @return dimension of the problem + */ + public int getDimension(); + + /** Get the current time derivative of the state vector. + * @param t current value of the independant time variable + * @param y array containing the current value of the state vector + * @param yDot array containing the current value of the first derivative + * of the state vector + * @param yDDot placeholder array where to put the second time derivative + * of the state vector + * @throws DerivativeException this exception is propagated to the caller if the + * underlying user function triggers one + */ + public void computeSecondDerivatives(double t, double[] y, double[] yDot, + double[] yDDot) + throws DerivativeException; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/SecondOrderIntegrator.java b/src/mantissa/src/org/spaceroots/mantissa/ode/SecondOrderIntegrator.java new file mode 100644 index 000000000..880717983 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/SecondOrderIntegrator.java @@ -0,0 +1,76 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +/** This interface represents a second order integrator for + * differential equations. + + *

    The classes which are devoted to solve second order differential + * equations should implement this interface. The problems which can + * be handled should implement the {@link + * SecondOrderDifferentialEquations} interface.

    + + * @see SecondOrderDifferentialEquations + + * @version $Id: SecondOrderIntegrator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public interface SecondOrderIntegrator { + + /** Get the name of the method. + * @return name of the method + */ + public String getName(); + + /** Set the step handler for this integrator. + * The handler will be called by the integrator for each accepted + * step. + * @param handler handler for the accepted steps + */ + public void setStepHandler (StepHandler handler); + + /** Get the step handler for this integrator. + * @return the step handler for this integrator + */ + public StepHandler getStepHandler(); + + /** Integrate the differential equations up to the given time + * @param equations differential equations to integrate + * @param t0 initial time + * @param y0 initial value of the state vector at t0 + * @param yDot0 initial value of the first derivative of the state + * vector at t0 + * @param t target time for the integration + * (can be set to a value smaller thant t0 for backward integration) + * @param y placeholder where to put the state vector at each + * successful step (and hence at the end of integration), can be the + * same object as y0 + * @param yDot placeholder where to put the first derivative of + * the state vector at time t, can be the same object as yDot0 + * @throws IntegratorException if the integrator cannot perform integration + * @throws DerivativeException this exception is propagated to the caller if the + * underlying user function triggers one + */ + public void integrate(SecondOrderDifferentialEquations equations, + double t0, double[] y0, double[] yDot0, + double t, double[] y, double[] yDot) + throws DerivativeException, IntegratorException; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/StepHandler.java b/src/mantissa/src/org/spaceroots/mantissa/ode/StepHandler.java new file mode 100644 index 000000000..c64cf59fd --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/StepHandler.java @@ -0,0 +1,79 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +/** + * This interface represents a handler that should be called after + * each successful step. + + *

    The ODE integrators compute the evolution of the state vector at + * some grid points that depend on their own internal algorithm. Once + * they have found a new grid point (possibly after having computed + * several evaluation of the derivative at intermediate points), they + * provide it to objects implementing this interface. These objects + * typically either ignore the intermediate steps and wait for the + * last one, store the points in an ephemeris, or forward them to + * specialized processing or output methods.

    + + * @see FirstOrderIntegrator + * @see SecondOrderIntegrator + * @see StepInterpolator + + * @version $Id: StepHandler.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public interface StepHandler { + + /** Determines whether this handler needs dense output. + *

    This method allows the integrator to avoid performing extra + * computation if the handler does not need dense output. If this + * method returns false, the integrator will call the {@link + * #handleStep} method with a {@link DummyStepInterpolator} rather + * than a custom interpolator.

    + * @return true if the handler needs dense output + */ + public boolean requiresDenseOutput(); + + /** Reset the step handler. + * Initialize the internal data as required before the first step is + * handled. + */ + public void reset(); + + /** + * Handle the last accepted step + * @param interpolator interpolator for the last accepted step. For + * efficiency purposes, the various integrators reuse the same + * object on each call, so if the instance wants to keep it across + * all calls (for example to provide at the end of the integration a + * continuous model valid throughout the integration range, as the + * {@link ContinuousOutputModel ContinuousOutputModel} class does), + * it should build a local copy using the clone method of the + * interpolator and store this copy. Keeping only a reference to the + * interpolator and reusing it will result in unpredictable + * behaviour (potentially crashing the application). + * @param isLast true if the step is the last one + * @throws DerivativeException this exception is propagated to the + * caller if the underlying user function triggers one + */ + public void handleStep(StepInterpolator interpolator, boolean isLast) + throws DerivativeException; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/StepInterpolator.java b/src/mantissa/src/org/spaceroots/mantissa/ode/StepInterpolator.java new file mode 100644 index 000000000..5d811d702 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/StepInterpolator.java @@ -0,0 +1,96 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +import java.io.Externalizable; + +/** This interface represents an interpolator over the last step + * during an ODE integration. + * + *

    The various ODE integrators provide objects implementing this + * interface to the step handlers. These objects are often custom + * objects tightly bound to the integrator internal algorithms. The + * handlers can use these objects to retrieve the state vector at + * intermediate times between the previous and the current grid points + * (this feature is often called dense output).

    + * + * @see FirstOrderIntegrator + * @see SecondOrderIntegrator + * @see StepHandler + * + * @version $Id: StepInterpolator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + * + */ + +public interface StepInterpolator + extends Externalizable { + + /** + * Get the previous grid point time. + * @return previous grid point time + */ + public double getPreviousTime(); + + /** + * Get the current grid point time. + * @return current grid point time + */ + public double getCurrentTime(); + + /** + * Get the time of the interpolated point. + * If {@link #setInterpolatedTime} has not been called, it returns + * the current grid point time. + * @return interpolation point time + */ + public double getInterpolatedTime(); + + /** + * Set the time of the interpolated point. + *

    Setting the time outside of the current step is now allowed + * (it was not allowed up to version 5.4 of Mantissa), but should be + * used with care since the accuracy of the interpolator will + * probably be very poor far from this step. This allowance has been + * added to simplify implementation of search algorithms near the + * step endpoints.

    + * @param time time of the interpolated point + * @throws DerivativeException if this call induces an automatic + * step finalization that throws one + */ + public void setInterpolatedTime(double time) + throws DerivativeException; + + /** + * Get the state vector of the interpolated point. + * @return state vector at time {@link #getInterpolatedTime} + */ + public double[] getInterpolatedState(); + + /** Check if the natural integration direction is forward. + *

    This method provides the integration direction as specified by + * the integrator itself, it avoid some nasty problems in + * degenerated cases like null steps due to cancellation at step + * initialization, step control or switching function + * triggering.

    + * @return true if the integration variable (time) increases during + * integration + */ + public boolean isForward(); + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/StepNormalizer.java b/src/mantissa/src/org/spaceroots/mantissa/ode/StepNormalizer.java new file mode 100644 index 000000000..9f9182fd0 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/StepNormalizer.java @@ -0,0 +1,159 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +/** + * This class wraps an object implementing {@link FixedStepHandler} + * into a {@link StepHandler}. + + *

    This wrapper allows to use fixed step handlers with general + * integrators which cannot guaranty their integration steps will + * remain constant and therefore only accept general step + * handlers.

    + + *

    The stepsize used is selected at construction time. The {@link + * FixedStepHandler#handleStep handleStep} method of the underlying + * {@link FixedStepHandler} object is called at the beginning time of + * the integration t0 and also at times t0+h, t0+2h, ... If the + * integration range is an integer multiple of the stepsize, then the + * last point handled will be the endpoint of the integration tend, if + * not, the last point will belong to the interval [tend - h ; + * tend].

    + + *

    There is no constraint on the integrator, it can use any + * timestep it needs (time steps longer or shorter than the fixed time + * step and non-integer ratios are all allowed).

    + + * @see StepHandler + * @see FixedStepHandler + + * @version $Id: StepNormalizer.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class StepNormalizer + implements StepHandler { + + /** Simple constructor. + * @param h fixed time step (sign is not used) + * @param handler fixed time step handler to wrap + */ + public StepNormalizer(double h, FixedStepHandler handler) { + this.h = Math.abs(h); + this.handler = handler; + reset(); + } + + /** Determines whether this handler needs dense output. + * This handler needs dense output in order to provide data at + * regularly spaced steps regardless of the steps the integrator + * uses, so this method always returns true. + * @return always true + */ + public boolean requiresDenseOutput() { + return true; + } + + /** Reset the step handler. + * Initialize the internal data as required before the first step is + * handled. + */ + public void reset() { + lastTime = Double.NaN; + lastState = null; + forward = true; + } + + /** + * Handle the last accepted step + * @param interpolator interpolator for the last accepted step. For + * efficiency purposes, the various integrators reuse the same + * object on each call, so if the instance wants to keep it across + * all calls (for example to provide at the end of the integration a + * continuous model valid throughout the integration range), it + * should build a local copy using the clone method and store this + * copy. + * @param isLast true if the step is the last one + * @throws DerivativeException this exception is propagated to the + * caller if the underlying user function triggers one + */ + public void handleStep(StepInterpolator interpolator, boolean isLast) + throws DerivativeException { + + double nextTime; + + if (lastState == null) { + + lastTime = interpolator.getPreviousTime(); + interpolator.setInterpolatedTime(lastTime); + + double[] state = interpolator.getInterpolatedState(); + lastState = new double[state.length]; + System.arraycopy(state, 0, lastState, 0, lastState.length); + + // take the integration direction into account + forward = (interpolator.getCurrentTime() >= lastTime); + if (! forward) { + h = -h; + } + + } + + nextTime = lastTime + h; + boolean nextInStep = forward ^ (nextTime > interpolator.getCurrentTime()); + while (nextInStep) { + + // output the stored previous step + handler.handleStep(lastTime, lastState, false); + + // store the next step + lastTime = nextTime; + interpolator.setInterpolatedTime(lastTime); + System.arraycopy(interpolator.getInterpolatedState(), 0, + lastState, 0, lastState.length); + + nextTime += h; + nextInStep = forward ^ (nextTime > interpolator.getCurrentTime()); + + } + + if (isLast) { + // there will be no more steps, + // the stored one should be flagged as being the last + handler.handleStep(lastTime, lastState, true); + } + + } + + /** Fixed time step. */ + private double h; + + /** Underlying step handler. */ + private FixedStepHandler handler; + + /** Last step time. */ + private double lastTime; + + /** Last State vector. */ + private double[] lastState; + + /** Integration direction indicator. */ + private boolean forward; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/SwitchState.java b/src/mantissa/src/org/spaceroots/mantissa/ode/SwitchState.java new file mode 100644 index 000000000..7c3879285 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/SwitchState.java @@ -0,0 +1,284 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +import org.spaceroots.mantissa.functions.FunctionException; +import org.spaceroots.mantissa.functions.scalar.ComputableFunction; + +import org.spaceroots.mantissa.roots.ConvergenceChecker; +import org.spaceroots.mantissa.roots.RootsFinder; +import org.spaceroots.mantissa.roots.BrentSolver; + +/** This class handles the state for one {@link SwitchingFunction + * switching function} during integration steps. + * + *

    Each time the integrator proposes a step, the switching function + * should be checked. This class handles the state of one function + * during one integration step, with references to the state at the + * end of the preceding step. This information is used to determine if + * the function should trigger an event or not during the proposed + * step (and hence the step should be reduced to ensure the event + * occurs at a bound rather than inside the step).

    + * + * @version $Id: SwitchState.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + * + */ +class SwitchState + implements ComputableFunction, ConvergenceChecker { + + /** Switching function. */ + private SwitchingFunction function; + + /** Maximal time interval between switching function checks. */ + private double maxCheckInterval; + + /** Convergence threshold for event localisation. */ + private double convergence; + + /** Time at the beginning of the step. */ + private double t0; + + /** Value of the switching function at the beginning of the step. */ + private double g0; + + /** Simulated sign of g0 (we cheat when crossing events). */ + private boolean g0Positive; + + /** Indicator of event expected during the step. */ + private boolean pendingEvent; + + /** Occurrence time of the pending event. */ + private double pendingEventTime; + + /** Occurrence time of the previous event. */ + private double previousEventTime; + + /** Variation direction around pending event. + * (this is considered with respect to the integration direction) + */ + private boolean increasing; + + /** Next action indicator. */ + private int nextAction; + + /** Interpolator valid for the current step. */ + private StepInterpolator interpolator; + + /** Simple constructor. + * @param function switching function + * @param maxCheckInterval maximal time interval between switching + * function checks (this interval prevents missing sign changes in + * case the integration steps becomes very large) + * @param convergence convergence threshold in the event time search + */ + public SwitchState(SwitchingFunction function, + double maxCheckInterval, double convergence) { + this.function = function; + this.maxCheckInterval = maxCheckInterval; + this.convergence = Math.abs(convergence); + + // some dummy values ... + t0 = Double.NaN; + g0 = Double.NaN; + g0Positive = true; + pendingEvent = false; + pendingEventTime = Double.NaN; + previousEventTime = Double.NaN; + increasing = true; + nextAction = SwitchingFunction.CONTINUE; + + interpolator = null; + + } + + /** Reinitialize the beginning of the step. + * @param t0 value of the independant time variable at the + * beginning of the step + * @param y0 array containing the current value of the state vector + * at the beginning of the step + */ + public void reinitializeBegin(double t0, double[] y0) { + this.t0 = t0; + g0 = function.g(t0, y0); + g0Positive = (g0 >= 0); + } + + /** Evaluate the impact of the proposed step on the switching function. + * @param interpolator step interpolator for the proposed step + * @return true if the switching function triggers an event before + * the end of the proposed step (this implies the step should be + * rejected) + */ + public boolean evaluateStep(StepInterpolator interpolator) { + + try { + + this.interpolator = interpolator; + + double t1 = interpolator.getCurrentTime(); + int n = Math.max(1, (int) Math.ceil(Math.abs(t1 - t0) / maxCheckInterval)); + double h = (t1 - t0) / n; + + double ta = t0; + double ga = g0; + double tb = t0 + ((t1 > t0) ? convergence : -convergence); + for (int i = 0; i < n; ++i) { + + // evaluate function value at the end of the substep + tb += h; + interpolator.setInterpolatedTime(tb); + double gb = function.g(tb, interpolator.getInterpolatedState()); + + // check events occurrence + if (g0Positive ^ (gb >= 0)) { + // there is a sign change: an event is expected during this step + + // variation direction, with respect to the integration direction + increasing = (gb >= ga); + + RootsFinder solver = new BrentSolver(); + if (solver.findRoot(this, this, 1000, ta, ga, tb, gb)) { + if (Double.isNaN(previousEventTime) + || (Math.abs(previousEventTime - solver.getRoot()) > convergence)) { + pendingEventTime = solver.getRoot(); + if (pendingEvent + && (Math.abs(t1 - pendingEventTime) <= convergence)) { + // we were already waiting for this event which was + // found during a previous call for a step that was + // rejected, this step must now be accepted since it + // properly ends exactly at the event occurrence + return false; + } + // either we were not waiting for the event or it has + // moved in such a way the step cannot be accepted + pendingEvent = true; + return true; + } + } else { + throw new RuntimeException("internal error"); + } + + } else { + // no sign change: there is no event for now + ta = tb; + ga = gb; + } + + } + + // no event during the whole step + pendingEvent = false; + pendingEventTime = Double.NaN; + return false; + + } catch (DerivativeException e) { + throw new RuntimeException("unexpected exception", e); + } catch (FunctionException e) { + throw new RuntimeException("unexpected exception", e); + } + + } + + /** Get the occurrence time of the event triggered in the current + * step. + * @return occurrence time of the event triggered in the current + * step. + */ + public double getEventTime() { + return pendingEventTime; + } + + /** Acknowledge the fact the step has been accepted by the integrator. + * @param t value of the independant time variable at the + * end of the step + * @param y array containing the current value of the state vector + * at the end of the step + */ + public void stepAccepted(double t, double[] y) { + + t0 = t; + g0 = function.g(t, y); + + if (pendingEvent) { + // force the sign to its value "just after the event" + previousEventTime = t; + g0Positive = increasing; + nextAction = function.eventOccurred(t, y); + } else { + g0Positive = (g0 >= 0); + nextAction = SwitchingFunction.CONTINUE; + } + } + + /** Check if the integration should be stopped at the end of the + * current step. + * @return true if the integration should be stopped + */ + public boolean stop() { + return nextAction == SwitchingFunction.STOP; + } + + /** Let the switching function reset the state if it wants. + * @param t value of the independant time variable at the + * beginning of the next step + * @param y array were to put the desired state vector at the beginning + * of the next step + */ + public void reset(double t, double[] y) { + if (pendingEvent) { + if (nextAction == SwitchingFunction.RESET) { + function.resetState(t, y); + } + pendingEvent = false; + pendingEventTime = Double.NaN; + } + } + + /** Get the value of the g function at the specified time. + * @param t current time + * @return g function value + * @exception FunctionException if the underlying interpolator is + * unable to interpolate the state at the specified time + */ + public double valueAt(double t) + throws FunctionException { + try { + interpolator.setInterpolatedTime(t); + return function.g(t, interpolator.getInterpolatedState()); + } catch (DerivativeException e) { + throw new FunctionException(e); + } + } + + /** Check if the event time has been found. + * @param x0 lower bound of the interval + * @param y0 value of the function at x0 + * @param x1 higher bound of the interval + * @param y1 value of the function at x1 + * @return convergence indicator + */ + public int converged(double x0, double y0, double x1, double y1) { + if (Math.abs(x1 - x0) < convergence) { + return (Math.abs(y0) < Math.abs(y1)) + ? ConvergenceChecker.LOW : ConvergenceChecker.HIGH; + } + return ConvergenceChecker.NONE; + } + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/SwitchingFunction.java b/src/mantissa/src/org/spaceroots/mantissa/ode/SwitchingFunction.java new file mode 100644 index 000000000..cf254eb8b --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/SwitchingFunction.java @@ -0,0 +1,127 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +/** This interface represents a switching function. + * + *

    A switching function allows to handle discrete events in + * integration problems. These events occur for example when the + * integration process should be stopped as some value is reached + * (G-stop facility), or when the derivatives has + * discontinuities. These events are traditionally defined as + * occurring when a g function sign changes.

    + * + *

    Since events are only problem-dependent and are triggered by the + * independant time variable and the state vector, they can + * occur at virtually any time. The integrators will take care to + * avoid sign changes inside the steps, they will reduce the step size + * when such an event is detected in order to put this event exactly + * at the end of the current step. This guarantees that step + * interpolation (which always has a one step scope) is relevant even + * in presence of discontinuities. This is independent from the + * stepsize control provided by integrators that monitor the local + * error (this feature is available on all integrators, including + * fixed step ones).

    + * + * @version $Id: SwitchingFunction.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + * + */ + +public interface SwitchingFunction { + + /** Stop indicator. + *

    This value should be used as the return value of the {@link + * #eventOccurred eventOccurred} method when the integration should be + * stopped after the event ending the current step.

    + */ + public static final int STOP = 0; + + /** Reset indicator. + *

    This value should be used as the return value of the {@link + * #eventOccurred eventOccurred} method when the integration should + * go on after the event ending the current step, with a new state + * vector (which will be retrieved through the {@link #resetState + * resetState} method).

    + */ + public static final int RESET = 1; + + /** Continue indicator. + *

    This value should be used as the return value of the {@link + * #eventOccurred eventOccurred} method when the integration should go + * on after the event ending the current step.

    + */ + public static final int CONTINUE = 2; + + /** Compute the value of the switching function. + + *

    Discrete events are generated when the sign of this function + * changes, the integrator will take care to change the stepsize in + * such a way these events occur exactly at step boundaries. This + * function must be continuous, as the integrator will need to find + * its roots to locate the events.

    + + * @param t current value of the independant time variable + * @param y array containing the current value of the state vector + * @return value of the g function + */ + public double g(double t, double[] y); + + /** Handle an event and choose what to do next. + + *

    This method is called when the integrator has accepted a step + * ending exactly on a sign change of the function, just before the + * step handler itself is called. It allows the user to update his + * internal data to acknowledge the fact the event has been handled + * (for example setting a flag to switch the derivatives computation + * in case of discontinuity), and it allows to direct the integrator + * to either stop or continue integration, possibly with a reset + * state.

    + + *

    If {@link #STOP} is returned, the step handler will be called + * with the isLast flag of the {@link + * StepHandler#handleStep handleStep} method set to true. If {@link + * #RESET} is returned, the {@link #resetState resetState} method + * will be called once the step handler has finished its task.

    + + * @param t current value of the independant time variable + * @param y array containing the current value of the state vector + * @return indication of what the integrator should do next, this + * value must be one of {@link #STOP}, {@link #RESET} or {@link + * #CONTINUE} + */ + public int eventOccurred(double t, double[] y); + + /** Reset the state prior to continue the integration. + + *

    This method is called after the step handler has returned and + * before the next step is started, but only when {@link + * #eventOccurred} has itself returned the {@link #RESET} + * indicator. It allows the user to reset the state vector for the + * next step, without perturbing the step handler of the finishing + * step. If the {@link #eventOccurred} never returns the {@link + * #RESET} indicator, this function will never be called, and it is + * safe to leave its body empty.

    + + * @param t current value of the independant time variable + * @param y array containing the current value of the state vector + * the new state should be put in the same array + */ + public void resetState(double t, double[] y); + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/SwitchingFunctionsHandler.java b/src/mantissa/src/org/spaceroots/mantissa/ode/SwitchingFunctionsHandler.java new file mode 100644 index 000000000..3c7a1756e --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/SwitchingFunctionsHandler.java @@ -0,0 +1,185 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +import org.spaceroots.mantissa.ode.DerivativeException; + +import java.util.ArrayList; +import java.util.Iterator; + +/** This class handles several {@link SwitchingFunction switching + * functions} during integration. + * + * @see SwitchingFunction + * + * @version $Id: SwitchingFunctionsHandler.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + * + */ + +public class SwitchingFunctionsHandler { + + /** Simple constructor. + * Create an empty handler + */ + public SwitchingFunctionsHandler() { + functions = new ArrayList(); + first = null; + initialized = false; + } + + /** Add a switching function. + * @param function switching function + * @param maxCheckInterval maximal time interval between switching + * function checks (this interval prevents missing sign changes in + * case the integration steps becomes very large) + * @param convergence convergence threshold in the event time search + */ + public void add(SwitchingFunction function, + double maxCheckInterval, double convergence) { + functions.add(new SwitchState(function, maxCheckInterval, convergence)); + } + + /** Check if the handler does not have any condition. + * @return true if handler is empty + */ + public boolean isEmpty() { + return functions.isEmpty(); + } + + /** Evaluate the impact of the proposed step on all handled + * switching functions. + * @param interpolator step interpolator for the proposed step + * @return true if at least one switching function triggers an event + * before the end of the proposed step (this implies the step should + * be rejected) + */ + public boolean evaluateStep(StepInterpolator interpolator) { + + try { + + first = null; + if (functions.isEmpty()) { + // there is nothing to do, return now to avoid setting the + // interpolator time (and hence avoid unneeded calls to the + // user function due to interpolator finalization) + return false; + } + + if (! initialized) { + + // initialize the switching functions + double t0 = interpolator.getPreviousTime(); + interpolator.setInterpolatedTime(t0); + double [] y = interpolator.getInterpolatedState(); + for (Iterator iter = functions.iterator(); iter.hasNext();) { + ((SwitchState) iter.next()).reinitializeBegin(t0, y); + } + + initialized = true; + + } + + // check events occurrence + for (Iterator iter = functions.iterator(); iter.hasNext();) { + + SwitchState state = (SwitchState) iter.next(); + if (state.evaluateStep(interpolator)) { + if (first == null) { + first = state; + } else { + if (interpolator.isForward()) { + if (state.getEventTime() < first.getEventTime()) { + first = state; + } + } else { + if (state.getEventTime() > first.getEventTime()) { + first = state; + } + } + } + } + + } + + return first != null; + + } catch (DerivativeException e) { + throw new RuntimeException("unexpected exception", e); + } + + } + + /** Get the occurrence time of the first event triggered in the + * last evaluated step. + * @return occurrence time of the first event triggered in the last + * evaluated step, or Double.NaN if no event is + * triggered + */ + public double getEventTime() { + return (first == null) ? Double.NaN : first.getEventTime(); + } + + /** Inform the switching functions that the step has been accepted + * by the integrator. + * @param t value of the independant time variable at the + * end of the step + * @param y array containing the current value of the state vector + * at the end of the step + */ + public void stepAccepted(double t, double[] y) { + for (Iterator iter = functions.iterator(); iter.hasNext();) { + ((SwitchState) iter.next()).stepAccepted(t, y); + } + } + + /** Check if the integration should be stopped at the end of the + * current step. + * @return true if the integration should be stopped + */ + public boolean stop() { + for (Iterator iter = functions.iterator(); iter.hasNext();) { + if (((SwitchState) iter.next()).stop()) { + return true; + } + } + return false; + } + + /** Let the switching functions reset the state if they want. + * @param t value of the independant time variable at the + * beginning of the next step + * @param y array were to put the desired state vector at the beginning + * of the next step + */ + public void reset(double t, double[] y) { + for (Iterator iter = functions.iterator(); iter.hasNext();) { + ((SwitchState) iter.next()).reset(t, y); + } + } + + /** Switching functions. */ + private ArrayList functions; + + /** First active switching function. */ + private SwitchState first; + + /** Initialization indicator. */ + private boolean initialized; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/ThreeEighthesIntegrator.java b/src/mantissa/src/org/spaceroots/mantissa/ode/ThreeEighthesIntegrator.java new file mode 100644 index 000000000..4e99e4798 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/ThreeEighthesIntegrator.java @@ -0,0 +1,80 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +/** + * This class implements the 3/8 fourth order Runge-Kutta + * integrator for Ordinary Differential Equations. + + *

    This method is an explicit Runge-Kutta method, its Butcher-array + * is the following one : + *

    + *    0  |  0    0    0    0
    + *   1/3 | 1/3   0    0    0
    + *   2/3 |-1/3   1    0    0
    + *    1  |  1   -1    1    0
    + *       |--------------------
    + *       | 1/8  3/8  3/8  1/8
    + * 
    + *

    + + * @see EulerIntegrator + * @see ClassicalRungeKuttaIntegrator + * @see GillIntegrator + * @see MidpointIntegrator + + * @version $Id: ThreeEighthesIntegrator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class ThreeEighthesIntegrator + extends RungeKuttaIntegrator { + + private static final String methodName = new String("3/8"); + + private static final double[] c = { + 1.0 / 3.0, 2.0 / 3.0, 1.0 + }; + + private static final double[][] a = { + { 1.0 / 3.0 }, + { -1.0 / 3.0, 1.0 }, + { 1.0, -1.0, 1.0 } + }; + + private static final double[] b = { + 1.0 / 8.0, 3.0 / 8.0, 3.0 / 8.0, 1.0 / 8.0 + }; + + /** Simple constructor. + * Build a 3/8 integrator with the given step. + * @param step integration step + */ + public ThreeEighthesIntegrator(double step) { + super(false, c, a, b, new ThreeEighthesStepInterpolator(), step); + } + + /** Get the name of the method. + * @return name of the method + */ + public String getName() { + return methodName; + } + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/ThreeEighthesStepInterpolator.java b/src/mantissa/src/org/spaceroots/mantissa/ode/ThreeEighthesStepInterpolator.java new file mode 100644 index 000000000..4572067eb --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/ThreeEighthesStepInterpolator.java @@ -0,0 +1,113 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +/** + * This class implements a step interpolator for the 3/8 fourth + * order Runge-Kutta integrator. + + *

    This interpolator allows to compute dense output inside the last + * step computed. The interpolation equation is consistent with the + * integration scheme : + + *

    + *   y(t_n + theta h) = y (t_n + h)
    + *                    - (1 - theta) (h/8) [ (1 - 7 theta + 8 theta^2) y'_1
    + *                                      + 3 (1 +   theta - 4 theta^2) y'_2
    + *                                      + 3 (1 +   theta)             y'_3
    + *                                      +   (1 +   theta + 4 theta^2) y'_4
    + *                                        ]
    + * 
    + + * where theta belongs to [0 ; 1] and where y'_1 to y'_4 are the four + * evaluations of the derivatives already computed during the + * step.

    + + * @see ThreeEighthesIntegrator + + * @version $Id: ThreeEighthesStepInterpolator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +class ThreeEighthesStepInterpolator + extends RungeKuttaStepInterpolator { + + /** Simple constructor. + * This constructor builds an instance that is not usable yet, the + * {@link AbstractStepInterpolator#reinitialize} method should be called + * before using the instance in order to initialize the internal arrays. This + * constructor is used only in order to delay the initialization in + * some cases. The {@link RungeKuttaIntegrator} class uses the + * prototyping design pattern to create the step interpolators by + * cloning an uninitialized model and latter initializing the copy. + */ + public ThreeEighthesStepInterpolator() { + } + + /** Copy constructor. + * @param interpolator interpolator to copy from. The copy is a deep + * copy: its arrays are separated from the original arrays of the + * instance + */ + public ThreeEighthesStepInterpolator(ThreeEighthesStepInterpolator interpolator) { + super(interpolator); + } + + /** + * Clone the instance. + * the copy is a deep copy: its arrays are separated from the + * original arrays of the instance + * @return a copy of the instance + */ + public Object clone() { + return new ThreeEighthesStepInterpolator(this); + } + + /** Compute the state at the interpolated time. + * This is the main processing method that should be implemented by + * the derived classes to perform the interpolation. + * @param theta normalized interpolation abscissa within the step + * (theta is zero at the previous time step and one at the current time step) + * @param oneMinusThetaH time gap between the interpolated time and + * the current time + * @throws DerivativeException this exception is propagated to the caller if the + * underlying user function triggers one + */ + protected void computeInterpolatedState(double theta, + double oneMinusThetaH) + throws DerivativeException { + + double fourTheta2 = 4 * theta * theta; + double s = oneMinusThetaH / 8.0; + double coeff1 = s * (1 - 7 * theta + 2 * fourTheta2); + double coeff2 = 3 * s * (1 + theta - fourTheta2); + double coeff3 = 3 * s * (1 + theta); + double coeff4 = s * (1 + theta + fourTheta2); + + for (int i = 0; i < interpolatedState.length; ++i) { + interpolatedState[i] = currentState[i] + - coeff1 * yDotK[0][i] - coeff2 * yDotK[1][i] + - coeff3 * yDotK[2][i] - coeff4 * yDotK[3][i]; + } + + } + + private static final long serialVersionUID = -3345024435978721931L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/doc-files/org_spaceroots_mantissa_ode_classes.png b/src/mantissa/src/org/spaceroots/mantissa/ode/doc-files/org_spaceroots_mantissa_ode_classes.png new file mode 100644 index 000000000..df5034b81 Binary files /dev/null and b/src/mantissa/src/org/spaceroots/mantissa/ode/doc-files/org_spaceroots_mantissa_ode_classes.png differ diff --git a/src/mantissa/src/org/spaceroots/mantissa/ode/package.html b/src/mantissa/src/org/spaceroots/mantissa/ode/package.html new file mode 100644 index 000000000..a51e145c8 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/ode/package.html @@ -0,0 +1,169 @@ + + +This package provides classes to solve Ordinary Differential Equations problems. + +

    +This package solves Initial Value Problems of the form +y'=f(t,y) with t0 and y(t0)=y0 +known. The provided integrators compute an estimate of +y(t) from t=t0 to t=t1. +

    + +

    +All integrators provide dense output. This means that besides +computing the state vector at discrete times, they also provide a +cheap mean to get the state between the time steps. They do so through +classes extending the {@link +org.spaceroots.mantissa.ode.StepInterpolator StepInterpolator} +abstract class, which are made available to the user at the end of +each step. +

    + +

    +All integrators handle multiple switching functions. This means that +the integrator can be driven by discrete events (occurring when the +signs of user-supplied {@link +org.spaceroots.mantissa.ode.SwitchingFunction switching functions} +change). The steps are shortened as needed to ensure the events occur +at step boundaries (even if the integrator is a fixed-step +integrator). When the events are triggered, integration can be stopped +(this is called a G-stop facility), the state vector can be changed, +or integration can simply go on. The latter case is useful to handle +discontinuities in the differential equations gracefully and get +accurate dense output even close to the discontinuity. The events are +detected when the functions signs are different at the beginning and +end of the current step, or at several equidistant points inside the +step if its length becomes larger than the maximal checking interval +specified for the given switching function. This time interval should +be set appropriately to avoid missing some switching function sign +changes (it is possible to set it to +Double.POSITIVE_INFINITY if the sign changes cannot be +missed). +

    + +

    +The user should describe his problem in his own classes +(UserProblem in the diagram below) which should implement +the {@link org.spaceroots.mantissa.ode.FirstOrderDifferentialEquations +FirstOrderDifferentialEquations} interface. Then he should pass it to +the integrator he prefers among all the classes that implement the +{@link org.spaceroots.mantissa.ode.FirstOrderIntegrator +FirstOrderIntegrator} interface. In order to simplify the mapping +between domain objects and the flat arrays needed in order to +implement the {@link +org.spaceroots.mantissa.ode.FirstOrderDifferentialEquations +FirstOrderDifferentialEquations} interface, the {@link +org.spaceroots.mantissa.utilities.ArraySliceMappable +ArraySliceMappable} interface and {@link +org.spaceroots.mantissa.utilities.ArrayMapper ArrayMapper} class +provided by the utilities package can be used. +

    + +

    +The solution of the integration problem is provided by two means. The +first one is aimed towards simple use: the state vector at the end of +the integration process is copied in the y array of the +{@link org.spaceroots.mantissa.ode.FirstOrderIntegrator#integrate +FirstOrderIntegrator.integrate} method. The second one should be used +when more in-depth information is needed throughout the integration +process. The user can register an object implementing the {@link +org.spaceroots.mantissa.ode.StepHandler StepHandler} interface or a +{@link org.spaceroots.mantissa.ode.StepNormalizer StepNormalizer} +object wrapping a user-specified object implementing the {@link +org.spaceroots.mantissa.ode.FixedStepHandler FixedStepHandler} +interface into the integrator before calling the {@link +org.spaceroots.mantissa.ode.FirstOrderIntegrator#integrate +FirstOrderIntegrator.integrate} method. The user object will be called +appropriately during the integration process, allowing the user to +process intermediate results. The default step handler does nothing. +

    + +

    +{@link org.spaceroots.mantissa.ode.ContinuousOutputModel +ContinuousOutputModel} is a special-purpose step handler that is able +to store all steps and to provide transparent access to any +intermediate result once the integration is over. An important feature +of this class is that it implements the Serializable +interface. This means that a complete continuous model of the +integrated function througout the integration range can be serialized +and reused later (if stored into a persistent medium like a filesystem +or a database) or elsewhere (if sent to another application). Only the +result of the integration is stored, there is no reference to the +integrated problem by itself. +

    + +

    +Other default implementations of the {@link +org.spaceroots.mantissa.ode.StepHandler StepHandler} interface are +available for general needs ({@link +org.spaceroots.mantissa.ode.DummyStepHandler DummyStepHandler}, {@link +org.spaceroots.mantissa.ode.StepNormalizer StepNormalizer}) and custom +implementations can be developped for specific needs. As an example, +if an application is to be completely driven by the integration +process, then most of the application code will be run inside a step +handler specific to this application. +

    + +

    +Some integrators (the simple ones) use fixed steps that are set at +creation time. The more efficient integrators use variable steps that +are handled internally in order to control the integration error with +respect to a specified accuracy (these integrators extend the {@link +org.spaceroots.mantissa.ode.AdaptiveStepsizeIntegrator +AdaptiveStepsizeIntegrator} abstract class). In this case, the step +handler which is called after each successful step shows up the +variable stepsize. The {@link +org.spaceroots.mantissa.ode.StepNormalizer StepNormalizer} class can +be used to convert the variable stepsize into a fixed stepsize that +can be handled by classes implementing the {@link +org.spaceroots.mantissa.ode.FixedStepHandler FixedStepHandler} +interface. Adaptive stepsize integrators can automatically compute the +initial stepsize by themselves, however the user can specify it if he +prefers to retain full control over the integration or if the +automatic guess is wrong. +

    + +

    + + + + + + + + +
    Fixed Step Integrators
    NameOrder
    {@link org.spaceroots.mantissa.ode.EulerIntegrator Euler}1
    {@link org.spaceroots.mantissa.ode.MidpointIntegrator Midpoint}2
    {@link org.spaceroots.mantissa.ode.ClassicalRungeKuttaIntegrator Classical Runge-Kutta}4
    {@link org.spaceroots.mantissa.ode.GillIntegrator Gill}4
    {@link org.spaceroots.mantissa.ode.ThreeEighthesIntegrator 3/8}4
    +

    + + + + + + + + +
    Adaptive Stepsize Integrators
    NameIntegration OrderError Estimation Order
    {@link org.spaceroots.mantissa.ode.HighamHall54Integrator Higham and Hall}54
    {@link org.spaceroots.mantissa.ode.DormandPrince54Integrator Dormand-Prince 5(4)}54
    {@link org.spaceroots.mantissa.ode.DormandPrince853Integrator Dormand-Prince 8(5,3)}85 and 3
    {@link org.spaceroots.mantissa.ode.GraggBulirschStoerIntegrator Gragg-Bulirsch-Stoer}variable (up to 18 by default)variable
    +

    + +

    +The class diagram below shows a typical example use of this +package. The orange boxes are the classes the user should develop, the +white boxes are the interfaces and classes already provided by the +library. The main application build an ODE problem that involve +several domain objects (for example the orbit of a spacecraft and its +attitude) which are mapped into one flat array representing the state +vector using an ArrayMapper object. The UserProblem object is provided +by the main application to an integrator (in this exemple the +Gragg-Bulirsch-Stoer integrator has been chosen) together with a step +handler (in this exemple the already existing ContinuousOutputModel +class has been chosen). In this case, the user waits until the end of +integration before continuing his own processing, and uses the +ContinuousOutputModel object to navigate throughout the integration +interval once it has been filled up by the integrator. +

    + + + +@author L. Maisonobe + + diff --git a/src/mantissa/src/org/spaceroots/mantissa/optimization/ConvergenceChecker.java b/src/mantissa/src/org/spaceroots/mantissa/optimization/ConvergenceChecker.java new file mode 100644 index 000000000..b84ee6540 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/optimization/ConvergenceChecker.java @@ -0,0 +1,42 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.optimization; + +/** This interface specifies how to check if a {@link + * DirectSearchOptimizer direct search method} has converged. + + *

    Deciding if convergence has been reached is a problem-dependent + * issue. The user should provide a class implementing this interface + * to allow the optimization algorithm to stop its search according to + * the problem at hand.

    + + * @version $Id: ConvergenceChecker.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public interface ConvergenceChecker { + + /** Check if the optimization algorithm has converged on the simplex. + * @param simplex ordered simplex (all points in the simplex have + * been eavluated and are sorted from lowest to largest cost) + * @return true if the algorithm is considered to have converged + */ + public boolean converged (PointCostPair[] simplex); + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/optimization/CostException.java b/src/mantissa/src/org/spaceroots/mantissa/optimization/CostException.java new file mode 100644 index 000000000..9c0ee9b36 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/optimization/CostException.java @@ -0,0 +1,66 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.optimization; + +import org.spaceroots.mantissa.MantissaException; + +/** This class represents exceptions thrown by cost functions. + + * @version $Id: CostException.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class CostException + extends MantissaException { + + /** Simple constructor. + * Build an exception with a default message + */ + public CostException() { + super("cost exception"); + } + + /** Simple constructor. + * Build an exception with the specified message + * @param message exception message + */ + public CostException(String message) { + super(message); + } + + /** Simple constructor. + * Build an exception from a cause + * @param cause cause of this exception + */ + public CostException(Throwable cause) { + super(cause); + } + + /** Simple constructor. + * Build an exception from a message and a cause + * @param message exception message + * @param cause cause of this exception + */ + public CostException(String message, Throwable cause) { + super(message, cause); + } + + private static final long serialVersionUID = -6099968585593678071L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/optimization/CostFunction.java b/src/mantissa/src/org/spaceroots/mantissa/optimization/CostFunction.java new file mode 100644 index 000000000..2d9b8d049 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/optimization/CostFunction.java @@ -0,0 +1,35 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.optimization; + +/** This interface represents a cost function to be minimized. + * @author Luc Maisonobe + * @version $Id: CostFunction.java 1705 2006-09-17 19:57:39Z luc $ + */ +public interface CostFunction { + + + /** Compute the cost associated to the given parameters array. + * @param x parameters array + * @return cost associated to the parameters array + * @exception CostException if no cost can be computed for the parameters + * @see PointCostPair + */ + public double cost(double[] x) throws CostException; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/optimization/DirectSearchOptimizer.java b/src/mantissa/src/org/spaceroots/mantissa/optimization/DirectSearchOptimizer.java new file mode 100644 index 000000000..be64ebd9b --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/optimization/DirectSearchOptimizer.java @@ -0,0 +1,583 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.optimization; + +import org.spaceroots.mantissa.random.RandomVectorGenerator; +import org.spaceroots.mantissa.random.UncorrelatedRandomVectorGenerator; +import org.spaceroots.mantissa.random.CorrelatedRandomVectorGenerator; +import org.spaceroots.mantissa.random.UniformRandomGenerator; +import org.spaceroots.mantissa.random.VectorialSampleStatistics; +import org.spaceroots.mantissa.random.NotPositiveDefiniteMatrixException; + +import java.util.Arrays; +import java.util.Comparator; + +/** This class implements simplex-based direct search optimization + * algorithms. + + *

    Direct search method only use cost function values, they don't + * need derivatives and don't either try to compute approximation of + * the derivatives. According to a 1996 paper by Margaret H. Wright + * (Direct + * Search Methods: Once Scorned, Now Respectable), they are used + * when either the computation of the derivative is impossible (noisy + * functions, unpredictable dicontinuities) or difficult (complexity, + * computation cost). In the first cases, rather than an optimum, a + * not too bad point is desired. In the latter cases, an + * optimum is desired but cannot be reasonably found. In all cases + * direct search methods can be useful.

    + + *

    Simplex-based direct search methods are based on comparison of + * the cost function values at the vertices of a simplex (which is a + * set of n+1 points in dimension n) that is updated by the algorithms + * steps.

    + + *

    The instances can be built either in single-start or in + * multi-start mode. Multi-start is a traditional way to try to avoid + * beeing trapped in a local minimum and miss the global minimum of a + * function. It can also be used to verify the convergence of an + * algorithm. In multi-start mode, the {@link #minimizes(CostFunction, + * int, ConvergenceChecker, double[], double[]) minimizes} + * method returns the best minimum found after all starts, and the + * {@link #getMinima getMinima} method can be used to retrieve all + * minima from all starts (including the one already provided by the + * {@link #minimizes(CostFunction, int, ConvergenceChecker, double[], + * double[]) minimizes} method).

    + + *

    This class is the base class performing the boilerplate simplex + * initialization and handling. The simplex update by itself is + * performed by the derived classes according to the implemented + * algorithms.

    + + * @author Luc Maisonobe + * @version $Id: DirectSearchOptimizer.java 1705 2006-09-17 19:57:39Z luc $ + * @see CostFunction + * @see NelderMead + * @see MultiDirectional + */ +public abstract class DirectSearchOptimizer { + + /** Simple constructor. + */ + protected DirectSearchOptimizer() { + } + + /** Minimizes a cost function. + *

    The initial simplex is built from two vertices that are + * considered to represent two opposite vertices of a box parallel + * to the canonical axes of the space. The simplex is the subset of + * vertices encountered while going from vertexA to vertexB + * travelling along the box edges only. This can be seen as a scaled + * regular simplex using the projected separation between the given + * points as the scaling factor along each coordinate axis.

    + *

    The optimization is performed in single-start mode.

    + * @param f cost function + * @param maxEvaluations maximal number of function calls for each + * start (note that the number will be checked after + * complete simplices have been evaluated, this means that in some + * cases this number will be exceeded by a few units, depending on + * the dimension of the problem) + * @param checker object to use to check for convergence + * @param vertexA first vertex + * @param vertexB last vertex + * @return the point/cost pairs giving the minimal cost + * @exception CostException if the cost function throws one during + * the search + * @exception NoConvergenceException if none of the starts did + * converge (it is not thrown if at least one start did converge) + */ + public PointCostPair minimizes(CostFunction f, int maxEvaluations, + ConvergenceChecker checker, + double[] vertexA, double[] vertexB) + throws CostException, NoConvergenceException { + + // set up optimizer + buildSimplex(vertexA, vertexB); + setSingleStart(); + + // compute minimum + return minimizes(f, maxEvaluations, checker); + + } + + /** Minimizes a cost function. + *

    The initial simplex is built from two vertices that are + * considered to represent two opposite vertices of a box parallel + * to the canonical axes of the space. The simplex is the subset of + * vertices encountered while going from vertexA to vertexB + * travelling along the box edges only. This can be seen as a scaled + * regular simplex using the projected separation between the given + * points as the scaling factor along each coordinate axis.

    + *

    The optimization is performed in multi-start mode.

    + * @param f cost function + * @param maxEvaluations maximal number of function calls for each + * start (note that the number will be checked after + * complete simplices have been evaluated, this means that in some + * cases this number will be exceeded by a few units, depending on + * the dimension of the problem) + * @param checker object to use to check for convergence + * @param vertexA first vertex + * @param vertexB last vertex + * @param starts number of starts to perform (including the + * first one), multi-start is disabled if value is less than or + * equal to 1 + * @param seed seed for the random vector generator + * @return the point/cost pairs giving the minimal cost + * @exception CostException if the cost function throws one during + * the search + * @exception NoConvergenceException if none of the starts did + * converge (it is not thrown if at least one start did converge) + */ + public PointCostPair minimizes(CostFunction f, int maxEvaluations, + ConvergenceChecker checker, + double[] vertexA, double[] vertexB, + int starts, long seed) + throws CostException, NoConvergenceException { + + // set up the simplex travelling around the box + buildSimplex(vertexA, vertexB); + + // we consider the simplex could have been produced by a generator + // having its mean value at the center of the box, the standard + // deviation along each axe beeing the corresponding half size + double[] mean = new double[vertexA.length]; + double[] standardDeviation = new double[vertexA.length]; + for (int i = 0; i < vertexA.length; ++i) { + mean[i] = 0.5 * (vertexA[i] + vertexB[i]); + standardDeviation[i] = 0.5 * Math.abs(vertexA[i] - vertexB[i]); + } + + RandomVectorGenerator rvg = + new UncorrelatedRandomVectorGenerator(mean, standardDeviation, + new UniformRandomGenerator(seed)); + setMultiStart(starts, rvg); + + // compute minimum + return minimizes(f, maxEvaluations, checker); + + } + + /** Minimizes a cost function. + *

    The simplex is built from all its vertices.

    + *

    The optimization is performed in single-start mode.

    + * @param f cost function + * @param maxEvaluations maximal number of function calls for each + * start (note that the number will be checked after + * complete simplices have been evaluated, this means that in some + * cases this number will be exceeded by a few units, depending on + * the dimension of the problem) + * @param checker object to use to check for convergence + * @param vertices array containing all vertices of the simplex + * @return the point/cost pairs giving the minimal cost + * @exception CostException if the cost function throws one during + * the search + * @exception NoConvergenceException if none of the starts did + * converge (it is not thrown if at least one start did converge) + */ + public PointCostPair minimizes(CostFunction f, int maxEvaluations, + ConvergenceChecker checker, + double[][] vertices) + throws CostException, NoConvergenceException { + + // set up optimizer + buildSimplex(vertices); + setSingleStart(); + + // compute minimum + return minimizes(f, maxEvaluations, checker); + + } + + /** Minimizes a cost function. + *

    The simplex is built from all its vertices.

    + *

    The optimization is performed in multi-start mode.

    + * @param f cost function + * @param maxEvaluations maximal number of function calls for each + * start (note that the number will be checked after + * complete simplices have been evaluated, this means that in some + * cases this number will be exceeded by a few units, depending on + * the dimension of the problem) + * @param checker object to use to check for convergence + * @param vertices array containing all vertices of the simplex + * @param starts number of starts to perform (including the + * first one), multi-start is disabled if value is less than or + * equal to 1 + * @param seed seed for the random vector generator + * @return the point/cost pairs giving the minimal cost + * @exception NotPositiveDefiniteMatrixException if the vertices + * array is degenerated + * @exception CostException if the cost function throws one during + * the search + * @exception NoConvergenceException if none of the starts did + * converge (it is not thrown if at least one start did converge) + */ + public PointCostPair minimizes(CostFunction f, int maxEvaluations, + ConvergenceChecker checker, + double[][] vertices, + int starts, long seed) + throws NotPositiveDefiniteMatrixException, + CostException, NoConvergenceException { + + // store the points into the simplex + buildSimplex(vertices); + + // compute the statistical properties of the simplex points + VectorialSampleStatistics statistics = new VectorialSampleStatistics(); + for (int i = 0; i < vertices.length; ++i) { + statistics.add(vertices[i]); + } + + RandomVectorGenerator rvg = + new CorrelatedRandomVectorGenerator(statistics.getMean(null), + statistics.getCovarianceMatrix(null), + new UniformRandomGenerator(seed)); + setMultiStart(starts, rvg); + + // compute minimum + return minimizes(f, maxEvaluations, checker); + + } + + /** Minimizes a cost function. + *

    The simplex is built randomly.

    + *

    The optimization is performed in single-start mode.

    + * @param f cost function + * @param maxEvaluations maximal number of function calls for each + * start (note that the number will be checked after + * complete simplices have been evaluated, this means that in some + * cases this number will be exceeded by a few units, depending on + * the dimension of the problem) + * @param checker object to use to check for convergence + * @param generator random vector generator + * @return the point/cost pairs giving the minimal cost + * @exception CostException if the cost function throws one during + * the search + * @exception NoConvergenceException if none of the starts did + * converge (it is not thrown if at least one start did converge) + */ + public PointCostPair minimizes(CostFunction f, int maxEvaluations, + ConvergenceChecker checker, + RandomVectorGenerator generator) + throws CostException, NoConvergenceException { + + // set up optimizer + buildSimplex(generator); + setSingleStart(); + + // compute minimum + return minimizes(f, maxEvaluations, checker); + + } + + /** Minimizes a cost function. + *

    The simplex is built randomly.

    + *

    The optimization is performed in multi-start mode.

    + * @param f cost function + * @param maxEvaluations maximal number of function calls for each + * start (note that the number will be checked after + * complete simplices have been evaluated, this means that in some + * cases this number will be exceeded by a few units, depending on + * the dimension of the problem) + * @param checker object to use to check for convergence + * @param generator random vector generator + * @param starts number of starts to perform (including the + * first one), multi-start is disabled if value is less than or + * equal to 1 + * @return the point/cost pairs giving the minimal cost + * @exception CostException if the cost function throws one during + * the search + * @exception NoConvergenceException if none of the starts did + * converge (it is not thrown if at least one start did converge) + */ + public PointCostPair minimizes(CostFunction f, int maxEvaluations, + ConvergenceChecker checker, + RandomVectorGenerator generator, + int starts) + throws CostException, NoConvergenceException { + + // set up optimizer + buildSimplex(generator); + setMultiStart(starts, generator); + + // compute minimum + return minimizes(f, maxEvaluations, checker); + + } + + /** Build a simplex from two extreme vertices. + *

    The two vertices are considered to represent two opposite + * vertices of a box parallel to the canonical axes of the + * space. The simplex is the subset of vertices encountered while + * going from vertexA to vertexB travelling along the box edges + * only. This can be seen as a scaled regular simplex using the + * projected separation between the given points as the scaling + * factor along each coordinate axis.

    + * @param vertexA first vertex + * @param vertexB last vertex + */ + private void buildSimplex(double[] vertexA, double[] vertexB) { + + int n = vertexA.length; + simplex = new PointCostPair[n + 1]; + + // set up the simplex travelling around the box + for (int i = 0; i <= n; ++i) { + double[] vertex = new double[n]; + if (i > 0) { + System.arraycopy(vertexB, 0, vertex, 0, i); + } + if (i < n) { + System.arraycopy(vertexA, i, vertex, i, n - i); + } + simplex[i] = new PointCostPair(vertex); + } + + } + + /** Build a simplex from all its points. + * @param vertices array containing all vertices of the simplex + */ + private void buildSimplex(double[][] vertices) { + int n = vertices.length - 1; + simplex = new PointCostPair[n + 1]; + for (int i = 0; i <= n; ++i) { + simplex[i] = new PointCostPair(vertices[i]); + } + } + + /** Build a simplex randomly. + * @param generator random vector generator + */ + private void buildSimplex(RandomVectorGenerator generator) { + + // use first vector size to compute the number of points + double[] vertex = generator.nextVector(); + int n = vertex.length; + simplex = new PointCostPair[n + 1]; + simplex[0] = new PointCostPair(vertex); + + // fill up the vertex + for (int i = 1; i <= n; ++i) { + simplex[i] = new PointCostPair(generator.nextVector()); + } + + } + + /** Set up single-start mode. + */ + private void setSingleStart() { + starts = 1; + generator = null; + minima = null; + } + + /** Set up multi-start mode. + * @param starts number of starts to perform (including the + * first one), multi-start is disabled if value is less than or + * equal to 1 + * @param generator random vector generator to use for restarts + */ + public void setMultiStart(int starts, RandomVectorGenerator generator) { + if (starts < 2) { + this.starts = 1; + this.generator = null; + minima = null; + } else { + this.starts = starts; + this.generator = generator; + minima = null; + } + } + + /** Get all the minima found during the last call to {@link + * #minimizes(CostFunction, int, ConvergenceChecker, double[], double[]) + * minimizes}. + *

    The optimizer stores all the minima found during a set of + * restarts when multi-start mode is enabled. The {@link + * #minimizes(CostFunction, int, ConvergenceChecker, double[], double[]) + * minimizes} method returns the best point only. This method + * returns all the points found at the end of each starts, including + * the best one already returned by the {@link #minimizes(CostFunction, + * int, ConvergenceChecker, double[], double[]) minimizes} method. + * The array as one element for each start as specified in the constructor + * (it has one element only if optimizer has been set up for single-start).

    + *

    The array containing the minima is ordered with the results + * from the runs that did converge first, sorted from lowest to + * highest minimum cost, and null elements corresponding to the runs + * that did not converge (all elements will be null if the {@link + * #minimizes(CostFunction, int, ConvergenceChecker, double[], double[]) + * minimizes} method throwed a {@link NoConvergenceException + * NoConvergenceException}).

    + * @return array containing the minima, or null if {@link + * #minimizes(CostFunction, int, ConvergenceChecker, double[], double[]) + * minimizes} has not been called + */ + public PointCostPair[] getMinima() { + return minima; + } + + /** Minimizes a cost function. + * @param f cost function + * @param maxEvaluations maximal number of function calls for each + * start (note that the number will be checked after + * complete simplices have been evaluated, this means that in some + * cases this number will be exceeded by a few units, depending on + * the dimension of the problem) + * @param checker object to use to check for convergence + * @return the point/cost pairs giving the minimal cost + * @exception CostException if the cost function throws one during + * the search + * @exception NoConvergenceException if none of the starts did + * converge (it is not thrown if at least one start did converge) + */ + private PointCostPair minimizes(CostFunction f, int maxEvaluations, + ConvergenceChecker checker) + throws CostException, NoConvergenceException { + + this.f = f; + minima = new PointCostPair[starts]; + + // multi-start loop + for (int i = 0; i < starts; ++i) { + + evaluations = 0; + evaluateSimplex(); + + for (boolean loop = true; loop;) { + if (checker.converged(simplex)) { + // we have found a minimum + minima[i] = simplex[0]; + loop = false; + } else if (evaluations >= maxEvaluations) { + // this start did not converge, try a new one + minima[i] = null; + loop = false; + } else { + iterateSimplex(); + } + } + + if (i < (starts - 1)) { + // restart + buildSimplex(generator); + } + + } + + // sort the minima from lowest cost to highest cost, followed by + // null elements + Arrays.sort(minima, pointCostPairComparator); + + // return the found point given the lowest cost + if (minima[0] == null) { + throw new NoConvergenceException("none of the {0} start points" + + " lead to convergence", + new String[] { + Integer.toString(starts) + }); + } + return minima[0]; + + } + + /** Compute the next simplex of the algorithm. + */ + protected abstract void iterateSimplex() + throws CostException; + + /** Evaluate the cost on one point. + *

    A side effect of this method is to count the number of + * function evaluations

    + * @param x point on which the cost function should be evaluated + * @return cost at the given point + * @exception CostException if no cost can be computed for the parameters + */ + protected double evaluateCost(double[] x) + throws CostException { + evaluations++; + return f.cost(x); + } + + /** Evaluate all the non-evaluated points of the simplex. + * @exception CostException if no cost can be computed for the parameters + */ + protected void evaluateSimplex() + throws CostException { + + // evaluate the cost at all non-evaluated simplex points + for (int i = 0; i < simplex.length; ++i) { + PointCostPair pair = simplex[i]; + if (! pair.isEvaluated()) { + pair.setCost(evaluateCost(pair.getPoint())); + } + } + + // sort the simplex from lowest cost to highest cost + Arrays.sort(simplex, pointCostPairComparator); + + } + + /** Replace the worst point of the simplex by a new point. + * @param pointCostPair point to insert + */ + protected void replaceWorstPoint(PointCostPair pointCostPair) { + int n = simplex.length - 1; + for (int i = 0; i < n; ++i) { + if (simplex[i].getCost() > pointCostPair.getCost()) { + PointCostPair tmp = simplex[i]; + simplex[i] = pointCostPair; + pointCostPair = tmp; + } + } + simplex[n] = pointCostPair; + } + + /** Comparator for {@link PointCostPair PointCostPair} objects. */ + private static Comparator pointCostPairComparator = new Comparator() { + public int compare(Object o1, Object o2) { + if (o1 == null) { + return (o2 == null) ? 0 : +1; + } else if (o2 == null) { + return -1; + } else { + double cost1 = ((PointCostPair) o1).getCost(); + double cost2 = ((PointCostPair) o2).getCost(); + return (cost1 < cost2) ? -1 : ((o1 == o2) ? 0 : +1); + } + } + }; + + /** Simplex. */ + protected PointCostPair[] simplex; + + /** Cost function. */ + private CostFunction f; + + /** Number of evaluations already performed. */ + private int evaluations; + + /** Number of starts to go. */ + private int starts; + + /** Random generator for multi-start. */ + private RandomVectorGenerator generator; + + /** Found minima. */ + private PointCostPair[] minima; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/optimization/MultiDirectional.java b/src/mantissa/src/org/spaceroots/mantissa/optimization/MultiDirectional.java new file mode 100644 index 000000000..f2f32526f --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/optimization/MultiDirectional.java @@ -0,0 +1,123 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.optimization; + +/** This class implements the multi-directional direct search method. + + * @author Luc Maisonobe + * @version $Id: MultiDirectional.java 1705 2006-09-17 19:57:39Z luc $ + * @see NelderMead + */ +public class MultiDirectional + extends DirectSearchOptimizer { + + /** Build a multi-directional optimizer with default coefficients. + *

    The default values are 2.0 for khi and 0.5 for gamma.

    + */ + public MultiDirectional() { + super(); + this.khi = 2.0; + this.gamma = 0.5; + } + + /** Build a multi-directional optimizer with specified coefficients. + * @param khi expansion coefficient + * @param gamma contraction coefficient + */ + public MultiDirectional(double khi, double gamma) { + super(); + this.khi = khi; + this.gamma = gamma; + } + + /** Compute the next simplex of the algorithm. + */ + protected void iterateSimplex() + throws CostException { + + while (true) { + + // save the original vertex + PointCostPair[] original = simplex; + double originalCost = original[0].getCost(); + + // perform a reflection step + double reflectedCost = evaluateNewSimplex(original, 1.0); + if (reflectedCost < originalCost) { + + // compute the expanded simplex + PointCostPair[] reflected = simplex; + double expandedCost = evaluateNewSimplex(original, khi); + if (reflectedCost <= expandedCost) { + // accept the reflected simplex + simplex = reflected; + } + + return; + + } + + // compute the contracted simplex + double contractedCost = evaluateNewSimplex(original, gamma); + if (contractedCost < originalCost) { + // accept the contracted simplex + return; + } + + } + + } + + /** Compute and evaluate a new simplex. + * @param original original simplex (to be preserved) + * @param coeff linear coefficient + * @return smallest cost in the transformed simplex + * @exception CostException if the function cannot be evaluated at + * some point + */ + private double evaluateNewSimplex(PointCostPair[] original, double coeff) + throws CostException { + + double[] xSmallest = original[0].getPoint(); + int n = xSmallest.length; + + // create the linearly transformed simplex + simplex = new PointCostPair[n + 1]; + simplex[0] = original[0]; + for (int i = 1; i <= n; ++i) { + double[] xOriginal = original[i].getPoint(); + double[] xTransformed = new double[n]; + for (int j = 0; j < n; ++j) { + xTransformed[j] = xSmallest[j] + coeff * (xSmallest[j] - xOriginal[j]); + } + simplex[i] = new PointCostPair(xTransformed); + } + + // evaluate it + evaluateSimplex(); + return simplex[0].getCost(); + + } + + /** Expansion coefficient. */ + private double khi; + + /** Contraction coefficient. */ + private double gamma; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/optimization/NelderMead.java b/src/mantissa/src/org/spaceroots/mantissa/optimization/NelderMead.java new file mode 100644 index 000000000..bf8d52cd6 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/optimization/NelderMead.java @@ -0,0 +1,184 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.optimization; + +/** This class implements the Nelder-Mead direct search method. + + * @author Luc Maisonobe + * @version $Id: NelderMead.java 1705 2006-09-17 19:57:39Z luc $ + * @see MultiDirectional + */ +public class NelderMead + extends DirectSearchOptimizer { + + /** Build a Nelder-Mead optimizer with default coefficients. + *

    The default coefficients are 1.0 for rho, 2.0 for khi and 0.5 + * for both gamma and sigma.

    + */ + public NelderMead() { + super(); + this.rho = 1.0; + this.khi = 2.0; + this.gamma = 0.5; + this.sigma = 0.5; + } + + /** Build a Nelder-Mead optimizer with specified coefficients. + * @param rho reflection coefficient + * @param khi expansion coefficient + * @param gamma contraction coefficient + * @param sigma shrinkage coefficient + */ + public NelderMead(double rho, double khi, double gamma, double sigma) { + super(); + this.rho = rho; + this.khi = khi; + this.gamma = gamma; + this.sigma = sigma; + } + + /** Compute the next simplex of the algorithm. + */ + protected void iterateSimplex() + throws CostException { + + // the simplex has n+1 point if dimension is n + int n = simplex.length - 1; + + // interesting costs + double smallest = simplex[0].getCost(); + double secondLargest = simplex[n-1].getCost(); + double largest = simplex[n].getCost(); + double[] xLargest = simplex[n].getPoint(); + + // compute the centroid of the best vertices + // (dismissing the worst point at index n) + double[] centroid = new double[n]; + for (int i = 0; i < n; ++i) { + double[] x = simplex[i].getPoint(); + for (int j = 0; j < n; ++j) { + centroid[j] += x[j]; + } + } + double scaling = 1.0 / n; + for (int j = 0; j < n; ++j) { + centroid[j] *= scaling; + } + + // compute the reflection point + double[] xR = new double[n]; + for (int j = 0; j < n; ++j) { + xR[j] = centroid[j] + rho * (centroid[j] - xLargest[j]); + } + double costR = evaluateCost(xR); + + if ((smallest <= costR) && (costR < secondLargest)) { + + // accept the reflected point + PointCostPair r = new PointCostPair(xR); + r.setCost(costR); + replaceWorstPoint(r); + + } else if (costR < smallest) { + + // compute the expansion point + double[] xE = new double[n]; + for (int j = 0; j < n; ++j) { + xE[j] = centroid[j] + khi * (xR[j] - centroid[j]); + } + double costE = evaluateCost(xE); + + if (costE < costR) { + // accept the expansion point + PointCostPair e = new PointCostPair(xE); + e.setCost(costE); + replaceWorstPoint(e); + } else { + // accept the reflected point + PointCostPair r = new PointCostPair(xR); + r.setCost(costR); + replaceWorstPoint(r); + } + + } else { + + if (costR < largest) { + + // perform an outside contraction + double[] xC = new double[n]; + for (int j = 0; j < n; ++j) { + xC[j] = centroid[j] + gamma * (xR[j] - centroid[j]); + } + double costC = evaluateCost(xC); + + if (costC <= costR) { + // accept the contraction point + PointCostPair c = new PointCostPair(xC); + c.setCost(costC); + replaceWorstPoint(c); + return; + } + + } else { + + // perform an inside contraction + double[] xC = new double[n]; + for (int j = 0; j < n; ++j) { + xC[j] = centroid[j] - gamma * (centroid[j] - xLargest[j]); + } + double costC = evaluateCost(xC); + + if (costC < largest) { + // accept the contraction point + PointCostPair c = new PointCostPair(xC); + c.setCost(costC); + replaceWorstPoint(c); + return; + } + + } + + // perform a shrink + double[] xSmallest = simplex[0].getPoint(); + for (int i = 1; i < simplex.length; ++i) { + PointCostPair pair = simplex[i]; + double[] x = pair.getPoint(); + for (int j = 0; j < n; ++j) { + x[j] = xSmallest[j] + sigma * (x[j] - xSmallest[j]); + } + pair.setCost(Double.NaN); + } + evaluateSimplex(); + + } + + } + + /** Reflection coefficient. */ + private double rho; + + /** Expansion coefficient. */ + private double khi; + + /** Contraction coefficient. */ + private double gamma; + + /** Shrinkage coefficient. */ + private double sigma; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/optimization/NoConvergenceException.java b/src/mantissa/src/org/spaceroots/mantissa/optimization/NoConvergenceException.java new file mode 100644 index 000000000..01639f404 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/optimization/NoConvergenceException.java @@ -0,0 +1,43 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.optimization; + +import org.spaceroots.mantissa.MantissaException; + +/** This class represents exceptions thrown by optimization algorithms. + + * @version $Id: NoConvergenceException.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class NoConvergenceException + extends MantissaException { + + /** Simple constructor. + * Build an exception by translating and formating a message + * @param specifier format specifier (to be translated) + * @param parts to insert in the format (no translation) + */ + public NoConvergenceException(String specifier, String[] parts) { + super(specifier, parts); + } + + private static final long serialVersionUID = 4854864422540042859L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/optimization/PointCostPair.java b/src/mantissa/src/org/spaceroots/mantissa/optimization/PointCostPair.java new file mode 100644 index 000000000..0da1cdddd --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/optimization/PointCostPair.java @@ -0,0 +1,89 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.optimization; + +/** This class holds a point and its associated cost. + *

    A cost/point pair is not evaluated at build time. Its associated + * cost set to Double.NaN until it is evaluated.

    + * @author Luc Maisonobe + * @version $Id: PointCostPair.java 1705 2006-09-17 19:57:39Z luc $ + * @see CostFunction + */ +public class PointCostPair { + + /** Build a point/cost pair with non-evaluated cost. + * @param point point coordinates + */ + public PointCostPair(double[] point) { + this.point = point; + cost = Double.NaN; + } + + /** Reset the point coordinates. + *

    Resetting the points coordinates automatically reset the cost + * to non-evaluated

    + * @param point new point coordinates + * @return old point coordinates (this can be re-used to put the + * coordinates of another point without re-allocating an array) + */ + public double[] setPoint(double[] point) { + double[] oldPoint = this.point; + this.point = point; + cost = Double.NaN; + return oldPoint; + } + + /** Get the point coordinates. + * @return point coordinates + */ + public double[] getPoint() { + return point; + } + + /** Set the cost. + * @param cost cost to store in the instance (can be + * Double.NaN to reset the instance to non-evaluated) + */ + public void setCost(double cost) { + this.cost = cost; + } + + /** Get the cost. + * @return cost associated to the point (or Double.NaN + * if the instance is not evaluated) + */ + public double getCost() { + return cost; + } + + /** Check if the cost has been evaluated. + *

    The cost is considered to be non-evaluated if it is + * Double.isNaN(pair.getCost()) would return true

    + * @return true if the cost has been evaluated + */ + public boolean isEvaluated() { + return ! Double.isNaN(cost); + } + + /** Point coordinates. */ + private double[] point; + + /** Cost associated to the point. */ + private double cost; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/quadrature/scalar/ComputableFunctionIntegrator.java b/src/mantissa/src/org/spaceroots/mantissa/quadrature/scalar/ComputableFunctionIntegrator.java new file mode 100644 index 000000000..7716cc5e9 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/quadrature/scalar/ComputableFunctionIntegrator.java @@ -0,0 +1,49 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.quadrature.scalar; + +import org.spaceroots.mantissa.functions.scalar.ComputableFunction; +import org.spaceroots.mantissa.functions.FunctionException; + +/** This interface represents an integrator for scalar functions. + + *

    The classes which are devoted to integrate scalar functions + * should implement this interface. The functions which can be handled + * should implement the {@link + * org.spaceroots.mantissa.functions.scalar.ComputableFunction + * ComputableFunction} interface.

    + + * @see org.spaceroots.mantissa.functions.scalar.ComputableFunction + + * @version $Id: ComputableFunctionIntegrator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public interface ComputableFunctionIntegrator { + /** Integrate a function over a defined range. + * @param f function to integrate + * @param a first bound of the range (can be lesser or greater than b) + * @param b second bound of the range (can be lesser or greater than a) + * @return value of the integral over the range + * @exception FunctionException if the underlying function throws one + */ + public double integrate(ComputableFunction f, double a, double b) + throws FunctionException; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/quadrature/scalar/EnhancedSimpsonIntegrator.java b/src/mantissa/src/org/spaceroots/mantissa/quadrature/scalar/EnhancedSimpsonIntegrator.java new file mode 100644 index 000000000..a3d8f035f --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/quadrature/scalar/EnhancedSimpsonIntegrator.java @@ -0,0 +1,57 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.quadrature.scalar; + +import org.spaceroots.mantissa.functions.FunctionException; +import org.spaceroots.mantissa.functions.ExhaustedSampleException; +import org.spaceroots.mantissa.functions.scalar.SampledFunctionIterator; + +/** This class implements an enhanced Simpson-like integrator. + + *

    A traditional Simpson integrator is based on a quadratic + * approximation of the function on three equally spaced points. This + * integrator does the same thing but can handle non-equally spaced + * points. If it is used on a regular sample, it behaves exactly as a + * traditional Simpson integrator.

    + + * @version $Id: EnhancedSimpsonIntegrator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class EnhancedSimpsonIntegrator + implements SampledFunctionIntegrator { + public double integrate(SampledFunctionIterator iter) + throws ExhaustedSampleException, FunctionException { + + EnhancedSimpsonIntegratorSampler sampler + = new EnhancedSimpsonIntegratorSampler(iter); + double sum = 0.0; + + try { + while (true) { + sum = sampler.nextSamplePoint().getY(); + } + } catch(ExhaustedSampleException e) { + } + + return sum; + + } + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/quadrature/scalar/EnhancedSimpsonIntegratorSampler.java b/src/mantissa/src/org/spaceroots/mantissa/quadrature/scalar/EnhancedSimpsonIntegratorSampler.java new file mode 100644 index 000000000..586f93bf3 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/quadrature/scalar/EnhancedSimpsonIntegratorSampler.java @@ -0,0 +1,100 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.quadrature.scalar; + +import org.spaceroots.mantissa.functions.scalar.*; +import org.spaceroots.mantissa.functions.FunctionException; +import org.spaceroots.mantissa.functions.ExhaustedSampleException; + +/** This class implements an enhanced Simpson integrator as a sample. + + *

    A traditional Simpson integrator is based on a quadratic + * approximation of the function on three equally spaced points. This + * integrator does the same thing but can handle non-equally spaced + * points. If it is used on a regular sample, it behaves exactly as a + * traditional Simpson integrator.

    + + * @see EnhancedSimpsonIntegrator + + * @version $Id: EnhancedSimpsonIntegratorSampler.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class EnhancedSimpsonIntegratorSampler + implements SampledFunctionIterator { + + /** Underlying sampled function iterator. */ + private SampledFunctionIterator iter; + + /** Next point. */ + private ScalarValuedPair next; + + /** Current running sum. */ + private double sum; + + /** Constructor. + * Build an integrator from an underlying sample iterator. + * @param iter iterator over the base function + */ + public EnhancedSimpsonIntegratorSampler(SampledFunctionIterator iter) + throws ExhaustedSampleException, FunctionException { + + this.iter = iter; + + // get the first point + next = iter.nextSamplePoint(); + + // initialize the sum + sum = 0.0; + + } + + public boolean hasNext() { + return iter.hasNext(); + } + + public ScalarValuedPair nextSamplePoint() + throws ExhaustedSampleException, FunctionException { + // performs one step of an enhanced Simpson scheme + ScalarValuedPair previous = next; + ScalarValuedPair current = iter.nextSamplePoint(); + + try { + next = iter.nextSamplePoint(); + + double h1 = current.getX() - previous.getX(); + double h2 = next.getX() - current.getX(); + double cP = (h1 + h2) * (2 * h1 - h2) / (6 * h1); + double cC = (h1 + h2) * (h1 + h2) * (h1 + h2) / (6 * h1 * h2); + double cN = (h1 + h2) * (2 * h2 - h1) / (6 * h2); + + sum += cP * previous.getY() + cC * current.getY() + cN * next.getY(); + + } catch(ExhaustedSampleException e) { + // we have an incomplete step at the end of the sample + // we use a trapezoid scheme for this last step + sum += 0.5 * (current.getX() - previous.getX()) * (previous.getY() + current.getY()); + return new ScalarValuedPair(current.getX(), sum); + } + + return new ScalarValuedPair(next.getX(), sum); + + } + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/quadrature/scalar/GaussLegendreIntegrator.java b/src/mantissa/src/org/spaceroots/mantissa/quadrature/scalar/GaussLegendreIntegrator.java new file mode 100644 index 000000000..281121c70 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/quadrature/scalar/GaussLegendreIntegrator.java @@ -0,0 +1,151 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.quadrature.scalar; + +import org.spaceroots.mantissa.functions.scalar.ComputableFunction; +import org.spaceroots.mantissa.functions.FunctionException; + +/** This class implements a Gauss-Legendre integrator. + + *

    Gauss-Legendre integrators are efficient integrators that can + * accurately integrate functions with few functions evaluations. A + * Gauss-Legendre integrator using an n-points quadrature formula can + * integrate exactly 2n-1 degree polynoms.

    + + *

    These integrators evaluate the function on n carefully chosen + * points in each step interval. These points are not evenly + * spaced. The function is never evaluated at the + * boundary points, which means it can be undefined at these + * points.

    + + * @version $Id: GaussLegendreIntegrator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class GaussLegendreIntegrator + implements ComputableFunctionIntegrator { + /** Build a Gauss-Legendre integrator. + + *

    A Gauss-Legendre integrator is a formula like: + *

    +   *    int (f) from -1 to +1 = Sum (ai * f(xi))
    +   * 
    + *

    + * + *

    The coefficients of the formula are computed as follow: + *

    +   *   let n be the desired number of points
    +   *   the xi are the roots of the degree n Legendre polynomial
    +   *   the ai are the integrals int (Li^2) from -1 to +1
    +   *   where Li (x) = Prod (x-xk)/(xi-xk) for k != i
    +   * 
    + *

    + * + *

    A formula in n points can integrate exactly polynoms of degree + * up to 2n-1.

    + * + * @param minPoints minimal number of points desired + * @param rawStep raw integration step (the precise step will be + * adjusted in order to have an integer number of steps in the + * integration range). + * */ + public GaussLegendreIntegrator(int minPoints, double rawStep) { + if (minPoints <= 2) { + weightedRoots = new double[][] { + { 1.0, -1.0 / Math.sqrt(3.0) }, + { 1.0, 1.0 / Math.sqrt(3.0) } + }; + } else if (minPoints <= 3) { + weightedRoots = new double[][] { + { 5.0 / 9.0, -Math.sqrt(0.6) }, + { 8.0 / 9.0, 0.0 }, + { 5.0 / 9.0, Math.sqrt(0.6) } + }; + } else if (minPoints <= 4) { + weightedRoots = new double[][] { + { (90.0 - 5.0 * Math.sqrt(30.0)) / 180.0, + -Math.sqrt((15.0 + 2.0 * Math.sqrt(30.0)) / 35.0) }, + { (90.0 + 5.0 * Math.sqrt(30.0)) / 180.0, + -Math.sqrt((15.0 - 2.0 * Math.sqrt(30.0)) / 35.0) }, + { (90.0 + 5.0 * Math.sqrt(30.0)) / 180.0, + Math.sqrt((15.0 - 2.0 * Math.sqrt(30.0)) / 35.0) }, + { (90.0 - 5.0 * Math.sqrt(30.0)) / 180.0, + Math.sqrt((15.0 + 2.0 * Math.sqrt(30.0)) / 35.0) } + }; + } else { + weightedRoots = new double[][] { + { (322.0 - 13.0 * Math.sqrt(70.0)) / 900.0, + -Math.sqrt((35.0 + 2.0 * Math.sqrt(70.0)) / 63.0) }, + { (322.0 + 13.0 * Math.sqrt(70.0)) / 900.0, + -Math.sqrt((35.0 - 2.0 * Math.sqrt(70.0)) / 63.0) }, + { 128.0 / 225.0, + 0.0 }, + { (322.0 + 13.0 * Math.sqrt(70.0)) / 900.0, + Math.sqrt((35.0 - 2.0 * Math.sqrt(70.0)) / 63.0) }, + { (322.0 - 13.0 * Math.sqrt(70.0)) / 900.0, + Math.sqrt((35.0 + 2.0 * Math.sqrt(70.0)) / 63.0) } + }; + } + + this.rawStep = rawStep; + + } + + /** Get the number of functions evaluation per step. + * @return number of function evaluation per step + */ + public int getEvaluationsPerStep() { + return weightedRoots.length; + } + + public double integrate(ComputableFunction f, double a, double b) + throws FunctionException { + + // swap the bounds if they are not in ascending order + if (b < a) { + double tmp = b; + b = a; + a = tmp; + } + + // adjust the step according to the bounds + long n = Math.round(0.5 + (b - a) / rawStep); + double step = (b - a) / n; + + // integrate over all elementary steps + double halfStep = step / 2.0; + double midPoint = a + halfStep; + double sum = 0.0; + for (long i = 0; i < n; ++i) { + for (int j = 0; j < weightedRoots.length; ++j) { + sum += weightedRoots[j][0] + * f.valueAt(midPoint + halfStep * weightedRoots[j][1]); + } + midPoint += step; + } + + return halfStep * sum; + + } + + double[][] weightedRoots; + + double rawStep; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/quadrature/scalar/RiemannIntegrator.java b/src/mantissa/src/org/spaceroots/mantissa/quadrature/scalar/RiemannIntegrator.java new file mode 100644 index 000000000..ae61f3271 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/quadrature/scalar/RiemannIntegrator.java @@ -0,0 +1,62 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.quadrature.scalar; + +import org.spaceroots.mantissa.functions.FunctionException; +import org.spaceroots.mantissa.functions.ExhaustedSampleException; +import org.spaceroots.mantissa.functions.scalar.SampledFunctionIterator; + +/** This class implements a Riemann integrator. + + *

    A Riemann integrator is a very simple one that assumes the + * function is constant over the integration step. Since it is very + * simple, this algorithm needs very small steps to achieve high + * accuracy, and small steps lead to numerical errors and + * instabilities.

    + + *

    This algorithm is almost never used and has been included in + * this package only as a simple template for more useful + * integrators.

    + + * @see TrapezoidIntegrator + + * @version $Id: RiemannIntegrator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class RiemannIntegrator + implements SampledFunctionIntegrator { + public double integrate(SampledFunctionIterator iter) + throws ExhaustedSampleException, FunctionException { + + RiemannIntegratorSampler sampler = new RiemannIntegratorSampler(iter); + double sum = 0.0; + + try { + while (true) { + sum = sampler.nextSamplePoint().getY(); + } + } catch(ExhaustedSampleException e) { + } + + return sum; + + } + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/quadrature/scalar/RiemannIntegratorSampler.java b/src/mantissa/src/org/spaceroots/mantissa/quadrature/scalar/RiemannIntegratorSampler.java new file mode 100644 index 000000000..dd360be87 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/quadrature/scalar/RiemannIntegratorSampler.java @@ -0,0 +1,87 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.quadrature.scalar; + +import org.spaceroots.mantissa.functions.scalar.*; +import org.spaceroots.mantissa.functions.FunctionException; +import org.spaceroots.mantissa.functions.ExhaustedSampleException; + +/** This class implements a Riemann integrator as a sample. + + *

    A Riemann integrator is a very simple one that assumes the + * function is constant over the integration step. Since it is very + * simple, this algorithm needs very small steps to achieve high + * accuracy, and small steps lead to numerical errors and + * instabilities.

    + + *

    This algorithm is almost never used and has been included in + * this package only as a simple template for more useful + * integrators.

    + + * @see RiemannIntegrator + + * @version $Id: RiemannIntegratorSampler.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class RiemannIntegratorSampler + implements SampledFunctionIterator { + + /** Underlying sample iterator. */ + private SampledFunctionIterator iter; + + /** Current point. */ + private ScalarValuedPair current; + + /** Current running sum. */ + private double sum; + + /** Constructor. + * Build an integrator from an underlying sample iterator. + * @param iter iterator over the base function + */ + public RiemannIntegratorSampler(SampledFunctionIterator iter) + throws ExhaustedSampleException, FunctionException { + + this.iter = iter; + + // get the first point + current = iter.nextSamplePoint(); + + // initialize the sum + sum = 0.0; + + } + + public boolean hasNext() { + return iter.hasNext(); + } + + public ScalarValuedPair nextSamplePoint() + throws ExhaustedSampleException, FunctionException { + // performs one step of a Riemann scheme + ScalarValuedPair previous = current; + current = iter.nextSamplePoint(); + sum += (current.getX() - previous.getX()) * previous.getY(); + + return new ScalarValuedPair(current.getX(), sum); + + } + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/quadrature/scalar/SampledFunctionIntegrator.java b/src/mantissa/src/org/spaceroots/mantissa/quadrature/scalar/SampledFunctionIntegrator.java new file mode 100644 index 000000000..56a4eafa5 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/quadrature/scalar/SampledFunctionIntegrator.java @@ -0,0 +1,48 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.quadrature.scalar; + +import org.spaceroots.mantissa.functions.scalar.SampledFunctionIterator; +import org.spaceroots.mantissa.functions.ExhaustedSampleException; +import org.spaceroots.mantissa.functions.FunctionException; + +/** This interface represents an integrator for scalar samples. + + *

    The classes which are devoted to integrate scalar samples + * should implement this interface.

    + + * @see org.spaceroots.mantissa.functions.scalar.SampledFunctionIterator + * @see ComputableFunctionIntegrator + + * @version $Id: SampledFunctionIntegrator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public interface SampledFunctionIntegrator { + /** Integrate a sample over its overall range + * @param iter iterator over the sample to integrate + * @return value of the integral over the sample range + * @exception ExhaustedSampleException if the sample does not have + * enough points for the integration scheme + * @exception FunctionException if the underlying sampled function throws one + */ + public double integrate(SampledFunctionIterator iter) + throws ExhaustedSampleException, FunctionException; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/quadrature/scalar/TrapezoidIntegrator.java b/src/mantissa/src/org/spaceroots/mantissa/quadrature/scalar/TrapezoidIntegrator.java new file mode 100644 index 000000000..ce2f3b908 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/quadrature/scalar/TrapezoidIntegrator.java @@ -0,0 +1,53 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.quadrature.scalar; + +import org.spaceroots.mantissa.functions.FunctionException; +import org.spaceroots.mantissa.functions.ExhaustedSampleException; +import org.spaceroots.mantissa.functions.scalar.SampledFunctionIterator; + +/** This class implements a trapezoid integrator. + + *

    A trapezoid integrator is a very simple one that assumes the + * function is linear over the integration step.

    + + * @version $Id: TrapezoidIntegrator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class TrapezoidIntegrator + implements SampledFunctionIntegrator { + public double integrate(SampledFunctionIterator iter) + throws ExhaustedSampleException, FunctionException { + + TrapezoidIntegratorSampler sampler = new TrapezoidIntegratorSampler(iter); + double sum = 0.0; + + try { + while (true) { + sum = sampler.nextSamplePoint().getY(); + } + } catch (ExhaustedSampleException e) { + } + + return sum; + + } + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/quadrature/scalar/TrapezoidIntegratorSampler.java b/src/mantissa/src/org/spaceroots/mantissa/quadrature/scalar/TrapezoidIntegratorSampler.java new file mode 100644 index 000000000..aee1588e7 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/quadrature/scalar/TrapezoidIntegratorSampler.java @@ -0,0 +1,89 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.quadrature.scalar; + +import org.spaceroots.mantissa.functions.scalar.*; +import org.spaceroots.mantissa.functions.FunctionException; +import org.spaceroots.mantissa.functions.ExhaustedSampleException; + +/** This class implements a trapezoid integrator as a sample. + + *

    A trapezoid integrator is a very simple one that assumes the + * function is constant over the integration step. Since it is very + * simple, this algorithm needs very small steps to achieve high + * accuracy, and small steps lead to numerical errors and + * instabilities.

    + + *

    This algorithm is almost never used and has been included in + * this package only as a simple template for more useful + * integrators.

    + + * @see TrapezoidIntegrator + + * @version $Id: TrapezoidIntegratorSampler.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class TrapezoidIntegratorSampler + implements SampledFunctionIterator { + + /** Underlying sample iterator. */ + private SampledFunctionIterator iter; + + /** Current point. */ + private ScalarValuedPair current; + + /** Current running sum. */ + private double sum; + + /** Constructor. + * Build an integrator from an underlying sample iterator. + * @param iter iterator over the base function + */ + public TrapezoidIntegratorSampler(SampledFunctionIterator iter) + throws ExhaustedSampleException, FunctionException { + + this.iter = iter; + + // get the first point + current = iter.nextSamplePoint(); + + // initialize the sum + sum = 0.0; + + } + + public boolean hasNext() { + return iter.hasNext (); + } + + public ScalarValuedPair nextSamplePoint() + throws ExhaustedSampleException, FunctionException { + // performs one step of a trapezoid scheme + ScalarValuedPair previous = current; + current = iter.nextSamplePoint(); + sum += 0.5 + * (current.getX() - previous.getX()) + * (previous.getY() + current.getY()); + + return new ScalarValuedPair(current.getX(), sum); + + } + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/quadrature/vectorial/ComputableFunctionIntegrator.java b/src/mantissa/src/org/spaceroots/mantissa/quadrature/vectorial/ComputableFunctionIntegrator.java new file mode 100644 index 000000000..fb482cc43 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/quadrature/vectorial/ComputableFunctionIntegrator.java @@ -0,0 +1,49 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.quadrature.vectorial; + +import org.spaceroots.mantissa.functions.vectorial.ComputableFunction; +import org.spaceroots.mantissa.functions.FunctionException; + +/** This interface represents an integrator for vectorial functions. + + *

    The classes which are devoted to integrate vectorial functions + * should implement this interface. The functions which can be handled + * should implement the {@link + * org.spaceroots.mantissa.functions.vectorial.ComputableFunction + * ComputableFunction} interface.

    + + * @see org.spaceroots.mantissa.functions.vectorial.ComputableFunction + + * @version $Id: ComputableFunctionIntegrator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public interface ComputableFunctionIntegrator { + /** Integrate a function over a defined range. + * @param f function to integrate + * @param a first bound of the range (can be lesser or greater than b) + * @param b second bound of the range (can be lesser or greater than a) + * @return value of the integral over the range + * @exception FunctionException if the underlying function throws one + */ + public double[] integrate(ComputableFunction f, double a, double b) + throws FunctionException; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/quadrature/vectorial/EnhancedSimpsonIntegrator.java b/src/mantissa/src/org/spaceroots/mantissa/quadrature/vectorial/EnhancedSimpsonIntegrator.java new file mode 100644 index 000000000..ce7d6200c --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/quadrature/vectorial/EnhancedSimpsonIntegrator.java @@ -0,0 +1,57 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.quadrature.vectorial; + +import org.spaceroots.mantissa.functions.FunctionException; +import org.spaceroots.mantissa.functions.ExhaustedSampleException; +import org.spaceroots.mantissa.functions.vectorial.SampledFunctionIterator; + +/** This class implements an enhanced Simpson-like integrator. + + *

    A traditional Simpson integrator is based on a quadratic + * approximation of the function on three equally spaced points. This + * integrator does the same thing but can handle non-equally spaced + * points. If it is used on a regular sample, it behaves exactly as a + * traditional Simpson integrator.

    + + * @version $Id: EnhancedSimpsonIntegrator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class EnhancedSimpsonIntegrator + implements SampledFunctionIntegrator { + public double[] integrate(SampledFunctionIterator iter) + throws ExhaustedSampleException, FunctionException { + + EnhancedSimpsonIntegratorSampler sampler = + new EnhancedSimpsonIntegratorSampler(iter); + double[] sum = null; + + try { + while (true) { + sum = sampler.nextSamplePoint().getY(); + } + } catch(ExhaustedSampleException e) { + } + + return sum; + + } + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/quadrature/vectorial/EnhancedSimpsonIntegratorSampler.java b/src/mantissa/src/org/spaceroots/mantissa/quadrature/vectorial/EnhancedSimpsonIntegratorSampler.java new file mode 100644 index 000000000..8f797aaed --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/quadrature/vectorial/EnhancedSimpsonIntegratorSampler.java @@ -0,0 +1,119 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.quadrature.vectorial; + +import org.spaceroots.mantissa.functions.vectorial.*; +import org.spaceroots.mantissa.functions.FunctionException; +import org.spaceroots.mantissa.functions.ExhaustedSampleException; + +/** This class implements an enhanced Simpson integrator as a sample. + + *

    A traditional Simpson integrator is based on a quadratic + * approximation of the function on three equally spaced points. This + * integrator does the same thing but can handle non-equally spaced + * points. If it is used on a regular sample, it behaves exactly as a + * traditional Simpson integrator.

    + + * @see EnhancedSimpsonIntegrator + + * @version $Id: EnhancedSimpsonIntegratorSampler.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class EnhancedSimpsonIntegratorSampler + implements SampledFunctionIterator { + + /** Underlying sample iterator. */ + private SampledFunctionIterator iter; + + /** Next point. */ + private VectorialValuedPair next; + + /** Current running sum. */ + private double[] sum; + + /** Constructor. + * Build an integrator from an underlying sample iterator. + * @param iter iterator over the base function + */ + public EnhancedSimpsonIntegratorSampler(SampledFunctionIterator iter) + throws ExhaustedSampleException, FunctionException { + + this.iter = iter; + + // get the first point + next = iter.nextSamplePoint(); + + // initialize the sum + sum = new double[iter.getDimension()]; + for (int i = 0; i < sum.length; ++i) { + sum[i] = 0.0; + } + + } + + public boolean hasNext() { + return iter.hasNext(); + } + + public int getDimension() { + return iter.getDimension(); + } + + public VectorialValuedPair nextSamplePoint() + throws ExhaustedSampleException, FunctionException { + // performs one step of an enhanced Simpson scheme + VectorialValuedPair previous = next; + VectorialValuedPair current = iter.nextSamplePoint(); + + try { + next = iter.nextSamplePoint(); + + double h1 = current.getX() - previous.getX(); + double h2 = next.getX() - current.getX(); + double cP = (h1 + h2) * (2 * h1 - h2) / (6 * h1); + double cC = (h1 + h2) * (h1 + h2) * (h1 + h2) / (6 * h1 * h2); + double cN = (h1 + h2) * (2 * h2 - h1) / (6 * h2); + + double[] pY = previous.getY(); + double[] cY = current.getY(); + double[] nY = next.getY(); + for (int i = 0; i < sum.length; ++i) { + sum [i] += cP * pY[i] + cC * cY[i] + cN * nY[i]; + } + + } catch(ExhaustedSampleException e) { + // we have an incomplete step at the end of the sample + // we use a trapezoid scheme for this last step + double halfDx = 0.5 * (current.getX() - previous.getX()); + double[] pY = previous.getY(); + double[] cY = current.getY(); + for (int i = 0; i < sum.length; ++i) { + sum [i] += halfDx * (pY[i] + cY[i]); + } + return new VectorialValuedPair(current.getX(), sum); + } + + double[] values = new double[sum.length]; + System.arraycopy(sum, 0, values, 0, sum.length); + return new VectorialValuedPair(next.getX(), values); + + } + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/quadrature/vectorial/GaussLegendreIntegrator.java b/src/mantissa/src/org/spaceroots/mantissa/quadrature/vectorial/GaussLegendreIntegrator.java new file mode 100644 index 000000000..667574e71 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/quadrature/vectorial/GaussLegendreIntegrator.java @@ -0,0 +1,162 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.quadrature.vectorial; + +import org.spaceroots.mantissa.functions.vectorial.ComputableFunction; +import org.spaceroots.mantissa.functions.FunctionException; + +/** This class implements a Gauss-Legendre integrator. + + *

    Gauss-Legendre integrators are efficient integrators that can + * accurately integrate functions with few functions evaluations. A + * Gauss-Legendre integrator using an n-points quadrature formula can + * integrate exactly 2n-1 degree polynoms.

    + + *

    These integrators evaluate the function on n carefully chosen + * points in each step interval. These points are not evenly + * spaced. The function is never evaluated at the + * boundary points, which means it can be undefined at these + * points.

    + + * @version $Id: GaussLegendreIntegrator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class GaussLegendreIntegrator + implements ComputableFunctionIntegrator { + /** Build a Gauss-Legendre integrator. + + *

    A Gauss-Legendre integrator is a formula like: + *

    +   *    int (f) from -1 to +1 = Sum (ai * f(xi))
    +   * 
    + *

    + * + *

    The coefficients of the formula are computed as follow: + *

    +   *   let n be the desired number of points
    +   *   the xi are the roots of the degree n Legendre polynomial
    +   *   the ai are the integrals int (Li^2) from -1 to +1
    +   *   where Li (x) = Prod (x-xk)/(xi-xk) for k != i
    +   * 
    + *

    + * + *

    A formula in n points can integrate exactly polynoms of degree + * up to 2n-1.

    + * + * @param minPoints minimal number of points desired + * @param rawStep raw integration step (the precise step will be + * adjusted in order to have an integer number of steps in the + * integration range). + * */ + public GaussLegendreIntegrator(int minPoints, double rawStep) { + if (minPoints <= 2) { + weightedRoots = new double[][] { + { 1.0, -1.0 / Math.sqrt (3.0) }, + { 1.0, 1.0 / Math.sqrt (3.0) } + }; + } else if (minPoints <= 3) { + weightedRoots = new double[][] { + { 5.0 / 9.0, -Math.sqrt (0.6) }, + { 8.0 / 9.0, 0.0 }, + { 5.0 / 9.0, Math.sqrt (0.6) } + }; + } else if (minPoints <= 4) { + weightedRoots = new double[][] { + { (90.0 - 5.0 * Math.sqrt (30.0)) / 180.0, + -Math.sqrt ((15.0 + 2.0 * Math.sqrt (30.0)) / 35.0) }, + { (90.0 + 5.0 * Math.sqrt (30.0)) / 180.0, + -Math.sqrt ((15.0 - 2.0 * Math.sqrt (30.0)) / 35.0) }, + { (90.0 + 5.0 * Math.sqrt (30.0)) / 180.0, + Math.sqrt ((15.0 - 2.0 * Math.sqrt (30.0)) / 35.0) }, + { (90.0 - 5.0 * Math.sqrt (30.0)) / 180.0, + Math.sqrt ((15.0 + 2.0 * Math.sqrt (30.0)) / 35.0) } + }; + } else { + weightedRoots = new double[][] { + { (322.0 - 13.0 * Math.sqrt (70.0)) / 900.0, + -Math.sqrt ((35.0 + 2.0 * Math.sqrt (70.0)) / 63.0) }, + { (322.0 + 13.0 * Math.sqrt (70.0)) / 900.0, + -Math.sqrt ((35.0 - 2.0 * Math.sqrt (70.0)) / 63.0) }, + { 128.0 / 225.0, + 0.0 }, + { (322.0 + 13.0 * Math.sqrt (70.0)) / 900.0, + Math.sqrt ((35.0 - 2.0 * Math.sqrt (70.0)) / 63.0) }, + { (322.0 - 13.0 * Math.sqrt (70.0)) / 900.0, + Math.sqrt ((35.0 + 2.0 * Math.sqrt (70.0)) / 63.0) } + }; + } + + this.rawStep = rawStep; + + } + + /** Get the number of functions evaluation per step. + * @return number of function evaluation per step + */ + public int getEvaluationsPerStep() { + return weightedRoots.length; + } + + public double[] integrate(ComputableFunction f, double a, double b) + throws FunctionException { + + // swap the integration bounds if they are not in ascending order + if (b < a) { + double tmp = b; + b = a; + a = tmp; + } + + // adjust the integration step according to the bounds + long n = Math.round(0.5 + (b - a) / rawStep); + double step = (b - a) / n; + + // integrate over all elementary steps + double halfStep = step / 2.0; + double midPoint = a + halfStep; + + double[] sum = new double[f.getDimension()]; + for (int k = 0; k < sum.length; ++k) { + sum[k] = 0.0; + } + + for (long i = 0; i < n; ++i) { + for (int j = 0; j < weightedRoots.length; ++j) { + double[] value = f.valueAt(midPoint + halfStep * weightedRoots[j][1]); + for (int k = 0; k < sum.length; ++k) { + sum[k] += weightedRoots[j][0] * value[k]; + } + } + midPoint += step; + } + + for (int k = 0; k < sum.length; ++k) { + sum [k] *= halfStep; + } + + return sum; + + } + + double[][] weightedRoots; + + double rawStep; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/quadrature/vectorial/RiemannIntegrator.java b/src/mantissa/src/org/spaceroots/mantissa/quadrature/vectorial/RiemannIntegrator.java new file mode 100644 index 000000000..a65fbf2c1 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/quadrature/vectorial/RiemannIntegrator.java @@ -0,0 +1,63 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.quadrature.vectorial; + +import org.spaceroots.mantissa.functions.FunctionException; +import org.spaceroots.mantissa.functions.ExhaustedSampleException; +import org.spaceroots.mantissa.functions.vectorial.SampledFunctionIterator; + +/** This class implements a Riemann integrator. + + *

    A Riemann integrator is a very simple one that assumes the + * function is constant over the integration step. Since it is very + * simple, this algorithm needs very small steps to achieve high + * accuracy, and small steps lead to numerical errors and + * instabilities.

    + + *

    This algorithm is almost never used and has been included in + * this package only as a simple template for more useful + * integrators.

    + + * @see TrapezoidIntegrator + + * @version $Id: RiemannIntegrator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class RiemannIntegrator + implements SampledFunctionIntegrator { + + public double[] integrate(SampledFunctionIterator iter) + throws ExhaustedSampleException, FunctionException { + + RiemannIntegratorSampler sampler = new RiemannIntegratorSampler(iter); + double[] sum = null; + + try { + while (true) { + sum = sampler.nextSamplePoint().getY(); + } + } catch(ExhaustedSampleException e) { + } + + return sum; + + } + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/quadrature/vectorial/RiemannIntegratorSampler.java b/src/mantissa/src/org/spaceroots/mantissa/quadrature/vectorial/RiemannIntegratorSampler.java new file mode 100644 index 000000000..7b41585f6 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/quadrature/vectorial/RiemannIntegratorSampler.java @@ -0,0 +1,101 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.quadrature.vectorial; + +import org.spaceroots.mantissa.functions.vectorial.*; +import org.spaceroots.mantissa.functions.FunctionException; +import org.spaceroots.mantissa.functions.ExhaustedSampleException; + +/** This class implements a Riemann integrator as a sample. + + *

    A Riemann integrator is a very simple one that assumes the + * function is constant over the integration step. Since it is very + * simple, this algorithm needs very small steps to achieve high + * accuracy, and small steps lead to numerical errors and + * instabilities.

    + + *

    This algorithm is almost never used and has been included in + * this package only as a simple template for more useful + * integrators.

    + + * @see RiemannIntegrator + + * @version $Id: RiemannIntegratorSampler.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class RiemannIntegratorSampler + implements SampledFunctionIterator { + + /** Underlying sample iterator. */ + private SampledFunctionIterator iter; + + /** Current point. */ + private VectorialValuedPair current; + + /** Current running sum. */ + private double[] sum; + + /** Constructor. + * Build an integrator from an underlying sample iterator. + * @param iter iterator over the base function + */ + public RiemannIntegratorSampler(SampledFunctionIterator iter) + throws ExhaustedSampleException, FunctionException { + + this.iter = iter; + + // get the first point + current = iter.nextSamplePoint(); + + // initialize the sum + sum = new double[iter.getDimension()]; + for (int i = 0; i < sum.length; ++i) { + sum[i] = 0.0; + } + + } + + public boolean hasNext() { + return iter.hasNext(); + } + + public int getDimension() { + return iter.getDimension(); + } + + public VectorialValuedPair nextSamplePoint() + throws ExhaustedSampleException, FunctionException { + + // performs one step of a Riemann scheme + VectorialValuedPair previous = current; + current = iter.nextSamplePoint(); + double step = (current.getX() - previous.getX()); + double[] pY = previous.getY(); + for (int i = 0; i < sum.length; ++i) { + sum[i] += step * pY[i]; + } + + double[] values = new double[sum.length]; + System.arraycopy(sum, 0, values, 0, sum.length); + return new VectorialValuedPair (current.getX(), values); + + } + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/quadrature/vectorial/SampledFunctionIntegrator.java b/src/mantissa/src/org/spaceroots/mantissa/quadrature/vectorial/SampledFunctionIntegrator.java new file mode 100644 index 000000000..4eca2a116 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/quadrature/vectorial/SampledFunctionIntegrator.java @@ -0,0 +1,49 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.quadrature.vectorial; + +import org.spaceroots.mantissa.functions.vectorial.SampledFunctionIterator; +import org.spaceroots.mantissa.functions.ExhaustedSampleException; +import org.spaceroots.mantissa.functions.FunctionException; + +/** This interface represents an integrator for vectorial samples. + + *

    The classes which are devoted to integrate vectorial samples + * should implement this interface.

    + + * @see org.spaceroots.mantissa.functions.vectorial.SampledFunctionIterator + * @see ComputableFunctionIntegrator + + * @version $Id: SampledFunctionIntegrator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public interface SampledFunctionIntegrator { + + /** Integrate a sample over its overall range + * @param iter iterator over the sample to integrate + * @return value of the integral over the sample range + * @exception ExhaustedSampleException if the sample does not have + * enough points for the integration scheme + * @exception FunctionException if the underlying sampled function throws one + */ + public double[] integrate(SampledFunctionIterator iter) + throws ExhaustedSampleException, FunctionException; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/quadrature/vectorial/TrapezoidIntegrator.java b/src/mantissa/src/org/spaceroots/mantissa/quadrature/vectorial/TrapezoidIntegrator.java new file mode 100644 index 000000000..0e75b4296 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/quadrature/vectorial/TrapezoidIntegrator.java @@ -0,0 +1,54 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.quadrature.vectorial; + +import org.spaceroots.mantissa.functions.FunctionException; +import org.spaceroots.mantissa.functions.ExhaustedSampleException; +import org.spaceroots.mantissa.functions.vectorial.SampledFunctionIterator; + +/** This class implements a trapezoid integrator. + + *

    A trapezoid integrator is a very simple one that assumes the + * function is linear over the integration step.

    + + * @version $Id: TrapezoidIntegrator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class TrapezoidIntegrator + implements SampledFunctionIntegrator { + public double[] integrate(SampledFunctionIterator iter) + throws ExhaustedSampleException, FunctionException { + + TrapezoidIntegratorSampler sampler = + new TrapezoidIntegratorSampler(iter); + double[] sum = null; + + try { + while (true) { + sum = sampler.nextSamplePoint().getY(); + } + } catch(ExhaustedSampleException e) { + } + + return sum; + + } + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/quadrature/vectorial/TrapezoidIntegratorSampler.java b/src/mantissa/src/org/spaceroots/mantissa/quadrature/vectorial/TrapezoidIntegratorSampler.java new file mode 100644 index 000000000..06b50e7e3 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/quadrature/vectorial/TrapezoidIntegratorSampler.java @@ -0,0 +1,103 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.quadrature.vectorial; + +import org.spaceroots.mantissa.functions.vectorial.*; +import org.spaceroots.mantissa.functions.FunctionException; +import org.spaceroots.mantissa.functions.ExhaustedSampleException; + +/** This class implements a trapezoid integrator as a sample. + + *

    A trapezoid integrator is a very simple one that assumes the + * function is constant over the integration step. Since it is very + * simple, this algorithm needs very small steps to achieve high + * accuracy, and small steps lead to numerical errors and + * instabilities.

    + + *

    This algorithm is almost never used and has been included in + * this package only as a simple template for more useful + * integrators.

    + + * @see TrapezoidIntegrator + + * @version $Id: TrapezoidIntegratorSampler.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class TrapezoidIntegratorSampler + implements SampledFunctionIterator { + + /** Underlying sample iterator. */ + private SampledFunctionIterator iter; + + /** Current point. */ + private VectorialValuedPair current; + + /** Current running sum. */ + private double[] sum; + + /** Constructor. + * Build an integrator from an underlying sample iterator. + * @param iter iterator over the base function + */ + public TrapezoidIntegratorSampler(SampledFunctionIterator iter) + throws ExhaustedSampleException, FunctionException { + + this.iter = iter; + + // get the first point + current = iter.nextSamplePoint(); + + // initialize the sum + sum = new double[iter.getDimension()]; + for (int i = 0; i < sum.length; ++i) { + sum[i] = 0.0; + } + + } + + public boolean hasNext() { + return iter.hasNext(); + } + + public int getDimension() { + return iter.getDimension(); + } + + public VectorialValuedPair nextSamplePoint() + throws ExhaustedSampleException, FunctionException { + + // performs one step of a trapezoid scheme + VectorialValuedPair previous = current; + current = iter.nextSamplePoint(); + + double halfDx = 0.5 * (current.getX() - previous.getX()); + double[] pY = previous.getY(); + double[] cY = current.getY(); + for (int i = 0; i < sum.length; ++i) { + sum[i] += halfDx * (pY[i] + cY[i]); + } + + double[] values = new double[sum.length]; + System.arraycopy(sum, 0, values, 0, sum.length); + return new VectorialValuedPair (current.getX(), values); + + } + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/random/CorrelatedRandomVectorGenerator.java b/src/mantissa/src/org/spaceroots/mantissa/random/CorrelatedRandomVectorGenerator.java new file mode 100644 index 000000000..bb3d03668 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/random/CorrelatedRandomVectorGenerator.java @@ -0,0 +1,287 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.random; + +import org.spaceroots.mantissa.MantissaException; +import org.spaceroots.mantissa.linalg.Matrix; +import org.spaceroots.mantissa.linalg.GeneralMatrix; +import org.spaceroots.mantissa.linalg.SymetricalMatrix; + +import java.io.Serializable; + +/** This class allows to generate random vectors with correlated components. + + *

    Random vectors with correlated components are built by combining + * the uncorrelated components of another random vector in such a way + * the resulting correlations are the ones specified by a positive + * definite covariance matrix.

    + + *

    Sometimes, the covariance matrix for a given simulation is not + * strictly positive definite. This means that the correlations are + * not all independant from each other. In this case, however, the non + * strictly positive elements found during the Cholesky decomposition + * of the covariance matrix should not be negative either, they + * should be null. This implies that rather than computing C = + * L.Lt where C is the covariance matrix and + * L is a lower-triangular matrix, we compute C = + * B.Bt where B is a rectangular matrix having + * more rows than columns. The number of columns of B is + * the rank of the covariance matrix, and it is the dimension of the + * uncorrelated random vector that is needed to compute the component + * of the correlated vector. This class does handle this situation + * automatically.

    + + * @version $Id: CorrelatedRandomVectorGenerator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class CorrelatedRandomVectorGenerator + implements Serializable, RandomVectorGenerator { + + /** Simple constructor. + *

    Build a correlated random vector generator from its mean + * vector and covariance matrix.

    + * @param mean expected mean values for all components + * @param covariance covariance matrix + * @param generator underlying generator for uncorrelated normalized + * components + * @exception IllegalArgumentException if there is a dimension + * mismatch between the mean vector and the covariance matrix + * @exception NotPositiveDefiniteMatrixException if the + * covariance matrix is not strictly positive definite + */ + public CorrelatedRandomVectorGenerator(double[] mean, + SymetricalMatrix covariance, + NormalizedRandomGenerator generator) + throws NotPositiveDefiniteMatrixException { + + int order = covariance.getRows(); + if (mean.length != order) { + String message = + MantissaException.translate("dimension mismatch {0} != {1}", + new String[] { + Integer.toString(mean.length), + Integer.toString(order) + }); + throw new IllegalArgumentException(message); + } + this.mean = mean; + + factorize(covariance); + + this.generator = generator; + normalized = new double[rank]; + correlated = new double[order]; + + } + + /** Simple constructor. + *

    Build a null mean random correlated vector generator from its + * covariance matrix.

    + * @param covariance covariance matrix + * @param generator underlying generator for uncorrelated normalized + * components + * @exception NotPositiveDefiniteMatrixException if the + * covariance matrix is not strictly positive definite + */ + public CorrelatedRandomVectorGenerator(SymetricalMatrix covariance, + NormalizedRandomGenerator generator) + throws NotPositiveDefiniteMatrixException { + + int order = covariance.getRows(); + mean = new double[order]; + for (int i = 0; i < order; ++i) { + mean[i] = 0; + } + + factorize(covariance); + + this.generator = generator; + normalized = new double[rank]; + correlated = new double[order]; + + } + + /** Get the root of the covariance matrix. + * The root is the matrix B such that B.Bt + * is equal to the covariance matrix + * @return root of the square matrix + */ + public Matrix getRootMatrix() { + return root; + } + + /** Get the underlying normalized components generator. + * @return underlying uncorrelated components generator + */ + public NormalizedRandomGenerator getGenerator() { + return generator; + } + + /** Get the rank of the covariance matrix. + * The rank is the number of independant rows in the covariance + * matrix, it is also the number of columns of the rectangular + * matrix of the factorization. + * @return rank of the square matrix. + */ + public int getRank() { + return rank; + } + + /** Factorize the original square matrix. + * @param covariance covariance matrix + * @exception NotPositiveDefiniteMatrixException if the + * covariance matrix is not strictly positive definite + */ + private void factorize(SymetricalMatrix covariance) + throws NotPositiveDefiniteMatrixException { + + int order = covariance.getRows(); + SymetricalMatrix c = (SymetricalMatrix) covariance.duplicate(); + GeneralMatrix b = new GeneralMatrix(order, order); + + int[] swap = new int[order]; + int[] index = new int[order]; + for (int i = 0; i < order; ++i) { + index[i] = i; + } + + rank = 0; + for (boolean loop = true; loop;) { + + // find maximal diagonal element + swap[rank] = rank; + for (int i = rank + 1; i < order; ++i) { + if (c.getElement(index[i], index[i]) + > c.getElement(index[swap[i]], index[swap[i]])) { + swap[rank] = i; + } + } + + + // swap elements + if (swap[rank] != rank) { + int tmp = index[rank]; + index[rank] = index[swap[rank]]; + index[swap[rank]] = tmp; + } + + // check diagonal element + if (c.getElement(index[rank], index[rank]) < 1.0e-12) { + + if (rank == 0) { + throw new NotPositiveDefiniteMatrixException(); + } + + // check remaining diagonal elements + for (int i = rank; i < order; ++i) { + if (c.getElement(index[rank], index[rank]) < -1.0e-12) { + // there is at least one sufficiently negative diagonal element, + // the covariance matrix is wrong + throw new NotPositiveDefiniteMatrixException(); + } + } + + // all remaining diagonal elements are close to zero, + // we consider we have found the rank of the covariance matrix + ++rank; + loop = false; + + } else { + + // transform the matrix + double sqrt = Math.sqrt(c.getElement(index[rank], index[rank])); + b.setElement(rank, rank, sqrt); + double inverse = 1 / sqrt; + for (int i = rank + 1; i < order; ++i) { + double e = inverse * c.getElement(index[i], index[rank]); + b.setElement(i, rank, e); + c.setElement(index[i], index[i], + c.getElement(index[i], index[i]) - e * e); + for (int j = rank + 1; j < i; ++j) { + double f = b.getElement(j, rank); + c.setElementAndSymetricalElement(index[i], index[j], + c.getElement(index[i], index[j]) + - e * f); + } + } + + // prepare next iteration + loop = ++rank < order; + + } + + } + + // build the root matrix + root = new GeneralMatrix(order, rank); + for (int i = 0; i < order; ++i) { + for (int j = 0; j < rank; ++j) { + root.setElement(swap[i], j, b.getElement(i, j)); + } + } + + } + + /** Generate a correlated random vector. + * @return a random vector as an array of double. The generator + * will reuse the same array for each call, in order to + * save the allocation time, so the user should keep a copy by + * himself if he needs so. + */ + public double[] nextVector() { + + // generate uncorrelated vector + for (int i = 0; i < rank; ++i) { + normalized[i] = generator.nextDouble(); + } + + // compute correlated vector + for (int i = 0; i < correlated.length; ++i) { + correlated[i] = mean[i]; + for (int j = 0; j < rank; ++j) { + correlated[i] += root.getElement(i, j) * normalized[j]; + } + } + + return correlated; + + } + + /** Mean vector. */ + private double[] mean; + + /** Permutated Cholesky root of the covariance matrix. */ + private Matrix root; + + /** Rank of the covariance matrix. */ + private int rank; + + /** Underlying generator. */ + NormalizedRandomGenerator generator; + + /** Storage for the normalized vector. */ + private double[] normalized; + + /** Storage for the random vector. */ + private double[] correlated; + + private static final long serialVersionUID = -4754497552287369719L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/random/GaussianRandomGenerator.java b/src/mantissa/src/org/spaceroots/mantissa/random/GaussianRandomGenerator.java new file mode 100644 index 000000000..d6ffcc66c --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/random/GaussianRandomGenerator.java @@ -0,0 +1,67 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.random; + +import java.util.Random; + +/** This class is a gaussian normalized random generator + * for scalars. + + *

    This class is a simple interface adaptor around the {@link + * java.util.Random#nextGaussian nextGaussian} method.

    + + * @version $Id: GaussianRandomGenerator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class GaussianRandomGenerator + implements NormalizedRandomGenerator { + + /** Underlying generator. */ + Random generator; + + /** Create a new generator. + * The seed of the generator is related to the current time. + */ + public GaussianRandomGenerator() { + generator = new Random(); + } + + /** Creates a new random number generator using a single int seed. + * @param seed the initial seed (32 bits integer) + */ + public GaussianRandomGenerator(int seed) { + generator = new Random(seed); + } + + /** Create a new generator initialized with a single long seed. + * @param seed seed for the generator (64 bits integer) + */ + public GaussianRandomGenerator(long seed) { + generator = new Random(seed); + } + + /** Generate a random scalar with null mean and unit standard deviation. + * @return a random scalar with null mean and unit standard deviation + */ + public double nextDouble() { + return generator.nextGaussian(); + } + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/random/NormalizedRandomGenerator.java b/src/mantissa/src/org/spaceroots/mantissa/random/NormalizedRandomGenerator.java new file mode 100644 index 000000000..be0fd7c66 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/random/NormalizedRandomGenerator.java @@ -0,0 +1,38 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.random; + +/** This interface represent a normalized random generator for + * scalars. + * Normalized generator should provide null mean and unit standard + * deviation scalars. + * @version $Id: NormalizedRandomGenerator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + */ +public interface NormalizedRandomGenerator { + + /** Generate a random scalar with null mean and unit standard deviation. + *

    This method does not specify the shape of the + * distribution, it is the implementing class that provides it. The + * only contract here is to generate numbers with null mean and unit + * standard deviation.

    + * @return a random scalar + */ + public double nextDouble(); + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/random/NotPositiveDefiniteMatrixException.java b/src/mantissa/src/org/spaceroots/mantissa/random/NotPositiveDefiniteMatrixException.java new file mode 100644 index 000000000..e79a786f3 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/random/NotPositiveDefiniteMatrixException.java @@ -0,0 +1,50 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.random; + +import org.spaceroots.mantissa.MantissaException; + +/** This class represents exceptions thrown by the correlated random + * vector generator. + + * @version $Id: NotPositiveDefiniteMatrixException.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class NotPositiveDefiniteMatrixException + extends MantissaException { + + /** Simple constructor. + * build an exception with a default message. + */ + public NotPositiveDefiniteMatrixException() { + super("not positive definite matrix"); + } + + /** Simple constructor. + * build an exception with the specified message. + * @param message message to use to build the exception + */ + public NotPositiveDefiniteMatrixException(String message) { + super(message); + } + + private static final long serialVersionUID = -6801349873804445905L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/random/RandomVectorGenerator.java b/src/mantissa/src/org/spaceroots/mantissa/random/RandomVectorGenerator.java new file mode 100644 index 000000000..c9ea8084a --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/random/RandomVectorGenerator.java @@ -0,0 +1,37 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.random; + +/** This interface represent a random generator for whole vectors. + + * @version $Id: RandomVectorGenerator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public interface RandomVectorGenerator { + + /** Generate a random vector. + * @return a random vector as an array of double. The generator + * will reuse the same array for each call, in order to + * save the allocation time, so the user should keep a copy by + * himself if he needs so. + */ + public double[] nextVector(); + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/random/ScalarSampleStatistics.java b/src/mantissa/src/org/spaceroots/mantissa/random/ScalarSampleStatistics.java new file mode 100644 index 000000000..0a2902dd1 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/random/ScalarSampleStatistics.java @@ -0,0 +1,161 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.random; + +/** This class compute basic statistics on a scalar sample. + * @version $Id: ScalarSampleStatistics.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + */ +public class ScalarSampleStatistics { + + /** Number of sample points. */ + private int n; + + /** Minimal value in the sample. */ + private double min; + + /** Maximal value in the sample. */ + private double max; + + /** Sum of the sample values. */ + private double sum; + + /** Sum of the squares of the sample values. */ + private double sum2; + + /** Simple constructor. + * Build a new empty instance + */ + public ScalarSampleStatistics() { + n = 0; + min = Double.NaN; + max = min; + sum = 0; + sum2 = 0; + } + + /** Add one point to the instance. + * @param x value of the sample point + */ + public void add(double x) { + + if (n++ == 0) { + min = x; + max = x; + sum = x; + sum2 = x * x; + } else { + + if (x < min) { + min = x; + } else if (x > max) { + max = x; + } + + sum += x; + sum2 += x * x; + + } + + } + + /** Add all points of an array to the instance. + * @param points array of points + */ + public void add(double[] points) { + for (int i = 0; i < points.length; ++i) { + add(points[i]); + } + } + + /** Add all the points of another sample to the instance. + * @param s sample to add + */ + public void add(ScalarSampleStatistics s) { + + if (s.n == 0) { + // nothing to add + return; + } + + if (n == 0) { + n = s.n; + min = s.min; + max = s.max; + sum = s.sum; + sum2 = s.sum2; + } else { + + n += s.n; + + if (s.min < min) { + min = s.min; + } else if (s.max > max) { + max = s.max; + } + + sum += s.sum; + sum2 += s.sum2; + + } + + } + + /** Get the number of points in the sample. + * @return number of points in the sample + */ + public int size() { + return n; + } + + /** Get the minimal value in the sample. + * @return minimal value in the sample + */ + public double getMin() { + return min; + } + + /** Get the maximal value in the sample. + * @return maximal value in the sample + */ + public double getMax() { + return max; + } + + /** Get the mean value of the sample. + * @return mean value of the sample + */ + public double getMean() { + return (n == 0) ? 0 : (sum / n); + } + + /** Get the standard deviation of the underlying probability law. + * This method estimate the standard deviation considering that the + * data available are only a sample of all possible + * values. This value is often called the sample standard deviation + * (as opposed to the population standard deviation). + * @return standard deviation of the underlying probability law + */ + public double getStandardDeviation() { + if (n < 2) { + return 0; + } + return Math.sqrt((n * sum2 - sum * sum) / (n * (n - 1))); + } + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/random/UncorrelatedRandomVectorGenerator.java b/src/mantissa/src/org/spaceroots/mantissa/random/UncorrelatedRandomVectorGenerator.java new file mode 100644 index 000000000..b624446b5 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/random/UncorrelatedRandomVectorGenerator.java @@ -0,0 +1,116 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.random; + +import java.io.Serializable; + +/** This class allows to generate random vectors with uncorrelated components. + + * @version $Id: UncorrelatedRandomVectorGenerator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class UncorrelatedRandomVectorGenerator + implements Serializable, RandomVectorGenerator { + + /** Simple constructor. + *

    Build an uncorrelated random vector generator from its mean + * and standard deviation vectors.

    + * @param mean expected mean values for all components + * @param standardDeviation standard deviation for all components + * @param generator underlying generator for uncorrelated normalized + * components + * @exception IllegalArgumentException if there is a dimension + * mismatch between the mean and standard deviation vectors + */ + public UncorrelatedRandomVectorGenerator(double[] mean, + double[] standardDeviation, + NormalizedRandomGenerator generator) { + + if (mean.length != standardDeviation.length) { + throw new IllegalArgumentException("dimension mismatch"); + } + this.mean = mean; + this.standardDeviation = standardDeviation; + + this.generator = generator; + random = new double[mean.length]; + + } + + /** Simple constructor. + *

    Build a null mean random and unit standard deviation + * uncorrelated vector generator

    + * @param dimension dimension of the vectors to generate + * @param generator underlying generator for uncorrelated normalized + * components + */ + public UncorrelatedRandomVectorGenerator(int dimension, + NormalizedRandomGenerator generator) { + + mean = new double[dimension]; + standardDeviation = new double[dimension]; + for (int i = 0; i < dimension; ++i) { + mean[i] = 0; + standardDeviation[i] = 1; + } + + this.generator = generator; + random = new double[dimension]; + + } + + /** Get the underlying normalized components generator. + * @return underlying uncorrelated components generator + */ + public NormalizedRandomGenerator getGenerator() { + return generator; + } + + /** Generate a correlated random vector. + * @return a random vector as an array of double. The generator + * will reuse the same array for each call, in order to + * save the allocation time, so the user should keep a copy by + * himself if he needs so. + */ + public double[] nextVector() { + + for (int i = 0; i < random.length; ++i) { + random[i] = mean[i] + standardDeviation[i] * generator.nextDouble(); + } + + return random; + + } + + /** Mean vector. */ + private double[] mean; + + /** Standard deviation vector. */ + private double[] standardDeviation; + + /** Underlying scalar generator. */ + NormalizedRandomGenerator generator; + + /** Storage for the random vector. */ + private double[] random; + + private static final long serialVersionUID = -3323293740860311151L; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/random/UniformRandomGenerator.java b/src/mantissa/src/org/spaceroots/mantissa/random/UniformRandomGenerator.java new file mode 100644 index 000000000..8b8a5cfa0 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/random/UniformRandomGenerator.java @@ -0,0 +1,74 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.random; + +import java.util.Random; + +/** This class implements a normalized uniform random generator. + + *

    Since this is a normalized random generator, it has a null mean + * and a unit standard deviation. Being also a uniform + * generator, it produces numbers in the range [-sqrt(3) ; + * sqrt(3)].

    + + * @version $Id: UniformRandomGenerator.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class UniformRandomGenerator + implements NormalizedRandomGenerator { + + private static final double SQRT3 = Math.sqrt(3.0); + + private static final double TWOSQRT3 = 2.0 * Math.sqrt(3.0); + + /** Underlying generator. */ + Random generator; + + /** Create a new generator. + * The seed of the generator is related to the current time. + */ + public UniformRandomGenerator() { + generator = new Random(); + } + + /** Creates a new random number generator using a single int seed. + * @param seed the initial seed (32 bits integer) + */ + public UniformRandomGenerator(int seed) { + generator = new Random(seed); + } + + /** Create a new generator initialized with a single long seed. + * @param seed seed for the generator (64 bits integer) + */ + public UniformRandomGenerator(long seed) { + generator = new Random(seed); + } + + /** Generate a random scalar with null mean and unit standard deviation. + *

    The number generated is uniformly distributed between -sqrt(3) + * and sqrt(3).

    + * @return a random scalar with null mean and unit standard deviation + */ + public double nextDouble() { + return TWOSQRT3 * generator.nextDouble() - SQRT3; + } + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/random/VectorialSampleStatistics.java b/src/mantissa/src/org/spaceroots/mantissa/random/VectorialSampleStatistics.java new file mode 100644 index 000000000..8bc79a5d2 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/random/VectorialSampleStatistics.java @@ -0,0 +1,313 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.random; + +import org.spaceroots.mantissa.linalg.SymetricalMatrix; + +import java.util.Arrays; + +/** This class compute basic statistics on a scalar sample. + * @version $Id: VectorialSampleStatistics.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + */ +public class VectorialSampleStatistics { + + /** Dimension of the vectors to handle. */ + private int dimension; + + /** Number of sample points. */ + private int n; + + /** Indices of the minimal values occurrence in the sample. */ + private int[] minIndices; + + /** Minimal value in the sample. */ + private double[] min; + + /** Maximal value in the sample. */ + private double[] max; + + /** Indices of the maximal values occurrence in the sample. */ + private int[] maxIndices; + + /** Sum of the sample values. */ + private double[] sum; + + /** Sum of the squares of the sample values. */ + private double[] sum2; + + /** Simple constructor. + * Build a new empty instance + */ + public VectorialSampleStatistics() { + dimension = -1; + n = 0; + min = null; + minIndices = null; + max = null; + maxIndices = null; + sum = null; + sum2 = null; + } + + /** Allocate all the arrays. */ + private void allocate() { + min = new double[dimension]; + minIndices = new int[dimension]; + max = new double[dimension]; + maxIndices = new int[dimension]; + sum = new double[dimension]; + sum2 = new double[dimension * (dimension + 1) / 2]; + } + + /** Add one point to the instance. + * @param x value of the sample point + * @exception IllegalArgumentException if there is a dimension + * mismatch between this point and the ones already added (this + * cannot happen when the instance is empty) + */ + public void add(double[] x) { + + if (n == 0) { + + dimension = x.length; + allocate(); + + Arrays.fill(minIndices, 0); + Arrays.fill(maxIndices, 0); + System.arraycopy(x, 0, min, 0, dimension); + System.arraycopy(x, 0, max, 0, dimension); + System.arraycopy(x, 0, sum, 0, dimension); + + int k = 0; + for (int i = 0; i < dimension; ++i) { + for (int j = 0; j <= i; ++j) { + sum2[k++] = x[i] * x[j]; + } + } + + } else { + int k = 0; + for (int i = 0; i < dimension; ++i) { + + if (x[i] < min[i]) { + min[i] = x[i]; + minIndices[i] = n; + } else if (x[i] > max[i]) { + max[i] = x[i]; + maxIndices[i] = n; + } + + sum[i] += x[i]; + for (int j = 0; j <= i; ++j) { + sum2[k++] += x[i] * x[j]; + } + + } + } + + ++n; + + } + + /** Add all points of an array to the instance. + * @param points array of points + * @exception IllegalArgumentException if there is a dimension + * mismatch between these points and the ones already added (this + * cannot happen when the instance is empty) + */ + public void add(double[][] points) { + for (int i = 0; i < points.length; ++i) { + add(points[i]); + } + } + + /** Add all the points of another sample to the instance. + * @param s samples to add + * @exception IllegalArgumentException if there is a dimension + * mismatch between this sample points and the ones already added + * (this cannot happen when the instance is empty) + */ + public void add(VectorialSampleStatistics s) { + + if (s.n == 0) { + // nothing to add + return; + } + + if (n == 0) { + + dimension = s.dimension; + allocate(); + + System.arraycopy(s.min, 0, min, 0, dimension); + System.arraycopy(s.minIndices, 0, minIndices, 0, dimension); + System.arraycopy(s.max, 0, max, 0, dimension); + System.arraycopy(s.maxIndices, 0, maxIndices, 0, dimension); + System.arraycopy(s.sum, 0, sum, 0, dimension); + System.arraycopy(s.sum2, 0, sum2, 0, sum2.length); + + } else { + int k = 0; + + for (int i = 0; i < dimension; ++i) { + + if (s.min[i] < min[i]) { + min[i] = s.min[i]; + minIndices[i] = n; + } else if (s.max[i] > max[i]) { + max[i] = s.max[i]; + maxIndices[i] = n; + } + + sum[i] += s.sum[i]; + for (int j = 0; j <= i; ++j) { + sum2[k] += s.sum2[k]; + ++k; + } + + } + + } + + n += s.n; + + } + + /** Get the number of points in the sample. + * @return number of points in the sample + */ + public int size() { + return n; + } + + /** Get the minimal value in the sample. + *

    Since all components of the sample vector can reach their + * minimal value at different times, this vector should be + * considered as gathering all minimas of all components. The index + * of the sample at which the minimum was encountered can be + * retrieved with the {@link #getMinIndices getMinIndices} + * method.

    + * @return minimal value in the sample (the array is a reference to + * an internal array that changes each time something is added to + * the instance, the caller should neither change it nor rely on its + * value in the long term) + * @see #getMinIndices + */ + public double[] getMin() { + return min; + } + + /** Get the indices at which the minimal value occurred in the sample. + * @return a vector reporting at which occurrence each component of + * the sample reached its minimal value (the array is a reference to + * an internal array that changes each time something is added to + * the instance, the caller should neither change it nor rely on its + * value in the long term) + * @see #getMin + */ + public int[] getMinIndices() { + return minIndices; + } + + /** Get the maximal value in the sample. + *

    Since all components of the sample vector can reach their + * maximal value at different times, this vector should be + * considered as gathering all maximas of all components. The index + * of the sample at which the maximum was encountered can be + * retrieved with the {@link #getMaxIndices getMaxIndices} + * method.

    + * @return maximal value in the sample (the array is a reference to + * an internal array that changes each time something is added to + * the instance, the caller should neither change it nor rely on its + * value in the long term) + * @see #getMaxIndices + */ + public double[] getMax() { + return max; + } + + /** Get the indices at which the maximal value occurred in the sample. + * @return a vector reporting at which occurrence each component of + * the sample reached its maximal value (the array is a reference to + * an internal array that changes each time something is added to + * the instance, the caller should neither change it nor rely on its + * value in the long term) + * @see #getMax + */ + public int[] getMaxIndices() { + return maxIndices; + } + + /** Get the mean value of the sample. + * @param mean placeholder where to store the array, if null a new + * array will be allocated + * @return mean value of the sample or null if the sample is empty + * and hence the dimension of the vectors is still unknown + * (reference to mean if it was non-null, reference to a new array + * otherwise) + */ + public double[] getMean(double[] mean) { + if (n == 0) { + return null; + } + if (mean == null) { + mean = new double[dimension]; + } + for (int i = 0; i < dimension; ++i) { + mean[i] = sum[i] / n; + } + return mean; + } + + /** Get the covariance matrix of the underlying law. + * This method estimate the covariance matrix considering that the + * data available are only a sample of all possible + * values. This value is the sample covariance matrix (as opposed + * to the population covariance matrix). + * @param covariance placeholder where to store the matrix, if null + * a new matrix will be allocated + * @return covariance matrix of the underlying or null if the + * sample has less than 2 points (reference to covariance if it was + * non-null, reference to a new matrix otherwise) + */ + public SymetricalMatrix getCovarianceMatrix(SymetricalMatrix covariance) { + + if (n < 2) { + return null; + } + + if (covariance == null) { + covariance = new SymetricalMatrix(dimension); + } + + double c = 1.0 / (n * (n - 1)); + int k = 0; + for (int i = 0; i < dimension; ++i) { + for (int j = 0; j <= i; ++j) { + double e = c * (n * sum2[k] - sum[i] * sum[j]); + covariance.setElementAndSymetricalElement(i, j, e); + ++k; + } + } + + return covariance; + + } + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/random/doc-files/org_spaceroots_mantissa_random_classes.png b/src/mantissa/src/org/spaceroots/mantissa/random/doc-files/org_spaceroots_mantissa_random_classes.png new file mode 100644 index 000000000..4230606f9 Binary files /dev/null and b/src/mantissa/src/org/spaceroots/mantissa/random/doc-files/org_spaceroots_mantissa_random_classes.png differ diff --git a/src/mantissa/src/org/spaceroots/mantissa/random/package.html b/src/mantissa/src/org/spaceroots/mantissa/random/package.html new file mode 100644 index 000000000..1b1f4b974 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/random/package.html @@ -0,0 +1,33 @@ + + +This package provides classes to perform some random draws and +statistical anaysis. + +

    The aim of this package is to provide the basic components needed +to generate random variables (correlated or not in the case of +vectorial variables) that could be used in a simulation application +and to provide some basic statistical classes in order to analyze the +simulation results.

    + +

    Vectorial generators are build by embedding a {@link +org.spaceroots.mantissa.random.NormalizedRandomGenerator normalized} +scalar generator into the vectorial generator classes {@link +org.spaceroots.mantissa.random.CorrelatedRandomVectorGenerator +CorrelatedRandomVectorGenerator} or {@link +org.spaceroots.mantissa.random.UncorrelatedRandomVectorGenerator +UncorrelatedRandomVectorGenerator} that will be responsible for +packaging all numbers into vectors with the specified mean values, standard +deviations and correlation coefficients. Since most practical problems +make the assumption the probability distribution is a gaussian one, +the normalized generator will often be an instance of {@link +org.spaceroots.mantissa.random.GaussianRandomGenerator +GaussianRandomGenerator}, but uniform distribution are also available +using instances of {@link +org.spaceroots.mantissa.random.UniformRandomGenerator +UniformRandomGenerator}.

    + + + +@author L. Maisonobe + + diff --git a/src/mantissa/src/org/spaceroots/mantissa/roots/BrentSolver.java b/src/mantissa/src/org/spaceroots/mantissa/roots/BrentSolver.java new file mode 100644 index 000000000..e5a660c36 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/roots/BrentSolver.java @@ -0,0 +1,188 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.roots; + +import org.spaceroots.mantissa.functions.scalar.ComputableFunction; +import org.spaceroots.mantissa.functions.FunctionException; + +/** This class implements the Brent algorithm to compute the roots of + * a function in an interval. + + * This class is basically a translation in Java of a fortran + * implementation found at netlib (zeroin.f). + + * @version $Id: BrentSolver.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class BrentSolver implements RootsFinder { + + /** IEEE 754 epsilon . */ + private static final double epsilon = Math.pow(2.0, -52); + + /** Root found. */ + private double root; + + /** Simple constructor. + * Build a Brent solver + */ + public BrentSolver() { + root = Double.NaN; + } + + /** Solve a function in a given interval known to contain a root. + * @param function function for which a root should be found + * @param checker checker for the convergence of the function + * @param maxIter maximal number of iteration allowed + * @param x0 abscissa of the lower bound of the interval + * @param f0 value of the function the lower bound of the interval + * @param x1 abscissa of the higher bound of the interval + * @param f1 value of the function the higher bound of the interval + * @return true if a root has been found in the given interval + */ + public boolean findRoot(ComputableFunction function, + ConvergenceChecker checker, + int maxIter, + double x0, double f0, double x1, double f1) + throws FunctionException { + + double a = x0; + double fa = f0; + double b = x1; + double fb = f1; + + double c = a; + double fc = fa; + + double d = b - a; + double e = d; + + double tolS; + for (int iter = 0; iter < maxIter; ++iter) { + + if (Math.abs(fc) < Math.abs(fb)) { + // invert points + a = b; + b = c; + c = a; + fa = fb; + fb = fc; + fc = fa; + } + + tolS = 2 * epsilon * Math.abs(b); + double xm = 0.5 * (c - b); + + // convergence test + double xLow, fLow, xHigh, fHigh; + if (b < c) { + xLow = b; + fLow = fb; + xHigh = c; + fHigh = fc; + } else { + xLow = c; + fLow = fc; + xHigh = b; + fHigh = fb; + } + + switch (checker.converged(xLow, fLow, xHigh, fHigh)) { + case ConvergenceChecker.LOW : + root = xLow; + return true; + case ConvergenceChecker.HIGH : + root = xHigh; + return true; + default : + if ((Math.abs(xm) < tolS) || (Math.abs(fb) < Double.MIN_VALUE)) { + root = b; + return true; + } + } + + if ((Math.abs(e) < tolS) || (Math.abs(fa) <= Math.abs(fb))) { + // use bisection method + d = xm; + e = d; + } else { + // use secant method + double p, q, r, s; + s = fb / fa; + if (Math.abs(a - c) < epsilon * Math.max(Math.abs(a), Math.abs(c))) { + // linear interpolation using only b and c points + p = 2.0 * xm * s; + q = 1.0 - s; + } else { + // inverse quadratic interpolation using a, b and c points + q = fa / fc; + r = fb / fc; + p = s * (2.0 * xm * q * (q - r) - (b - a) * (r - 1.0)); + q = (q - 1.0) * (r - 1.0) * (s - 1.0); + } + + // signs adjustment + if (p > 0.0) { + q = -q; + } else { + p = -p; + } + + // is interpolation acceptable ? + if (((2.0 * p) < (3.0 * xm * q - Math.abs(tolS * q))) + && + (p < Math.abs(0.5 * e * q))) { + e = d; + d = p / q; + } else { + // no, we need to fall back to bisection + d = xm; + e = d; + } + } + + // complete step + a = b; + fa = fb; + b += ((Math.abs(d) > tolS) ? d : (xm > 0.0 ? tolS : -tolS)); + fb = function.valueAt(b); + + if (fb * fc > 0) { + c = a; + fc = fa; + d = b - a; + e = d; + } + + } + + // we have exceeded the maximal number of iterations + return false; + + } + + /** Get the abscissa of the root. + * @return abscissa of the root + */ + public double getRoot() { + return root; + } + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/roots/ConvergenceChecker.java b/src/mantissa/src/org/spaceroots/mantissa/roots/ConvergenceChecker.java new file mode 100644 index 000000000..72c2e98ca --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/roots/ConvergenceChecker.java @@ -0,0 +1,57 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.roots; + +/** This interface specifies methods to check if a root-finding + * algorithm has converged. + + * Deciding if convergence has been reached is a problem-dependent + * issue. The user should provide a class implementing this interface + * to allow the root-finding algorithm to stop its search according to + * the problem at hand. + + * @version $Id: ConvergenceChecker.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public interface ConvergenceChecker { + + /** Indicator for no convergence. */ + public static final int NONE = 0; + + /** Indicator for convergence on the lower bound of the interval. */ + public static final int LOW = 1; + + /** Indicator for convergence on the higher bound of the interval. */ + public static final int HIGH = 2; + + /** Check if the root-finding algorithm has converged on the interval. + * The interval defined by the arguments contains one root (if there + * was at least one in the initial interval given by the user to the + * root-finding algorithm, of course) + * @param xLow abscissa of the lower bound of the interval + * @param fLow value of the function the lower bound of the interval + * @param xHigh abscissa of the higher bound of the interval + * @param fHigh value of the function the higher bound of the interval + * @return convergence indicator, must be one of {@link #NONE}, + * {@link #LOW} or {@link #HIGH} + */ + public int converged (double xLow, double fLow, double xHigh, double fHigh); + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/roots/RootsFinder.java b/src/mantissa/src/org/spaceroots/mantissa/roots/RootsFinder.java new file mode 100644 index 000000000..7c1401a64 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/roots/RootsFinder.java @@ -0,0 +1,54 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.roots; + +import org.spaceroots.mantissa.functions.scalar.ComputableFunction; +import org.spaceroots.mantissa.functions.FunctionException; + +/** This interface specifies root-finding methods for scalar + * functions. + + * @version $Id: RootsFinder.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public interface RootsFinder { + + /** Solve a function in a given interval known to contain a root. + * @param function function for which a root should be found + * @param checker checker for the convergence of the function + * @param maxIter maximal number of iteration allowed + * @param x0 abscissa of the lower bound of the interval + * @param f0 value of the function the lower bound of the interval + * @param x1 abscissa of the higher bound of the interval + * @param f1 value of the function the higher bound of the interval + * @return true if a root has been found in the given interval + */ + public boolean findRoot(ComputableFunction function, + ConvergenceChecker checker, + int maxIter, + double x0, double f0, double x1, double f1) + throws FunctionException; + + /** Get the abscissa of the root. + * @return abscissa of the root + */ + public double getRoot(); + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/utilities/ArrayMapper.java b/src/mantissa/src/org/spaceroots/mantissa/utilities/ArrayMapper.java new file mode 100644 index 000000000..fd74156d9 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/utilities/ArrayMapper.java @@ -0,0 +1,141 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.utilities; + +import java.util.ArrayList; +import java.util.Iterator; + +/** + * This class dispatch data between an array and several domain objects. + + * This class handles all the burden of mapping each domain object it + * handles to a slice of a single array. + + * @see ArraySliceMappable + + * @version $Id: ArrayMapper.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class ArrayMapper { + + /** Simple constructor. + * Build an empty array mapper + */ + public ArrayMapper() { + domainObjects = new ArrayList(); + size = 0; + internalData = null; + } + + /** Simple constructor. + * Build an array mapper managing one object. Other objects can be + * added later using the {@link #manageMappable manageMappable} + * method. This call is equivalent to build the mapper with the + * default constructor and adding the object. + * @param object domain object to handle + */ + public ArrayMapper(ArraySliceMappable object) { + + domainObjects = new ArrayList(); + domainObjects.add(new ArrayMapperEntry(object, 0)); + + size = object.getStateDimension(); + + internalData = new double [size]; + + } + + /** Take a new domain object into account. + * @param object domain object to handle + */ + public void manageMappable(ArraySliceMappable object) { + + domainObjects.add(new ArrayMapperEntry(object, size)); + + size += object.getStateDimension(); + + if (internalData != null) { + internalData = new double [size]; + } + + } + + /** Get the internal data array. + * @return internal data array + */ + public double[] getInternalDataArray() { + if (internalData == null) { + internalData = new double [size]; + } + return internalData; + } + + /** Map data from the internal array to the domain objects. + */ + public void updateObjects() { + if (internalData == null) { + internalData = new double [size]; + } + updateObjects(internalData); + } + + /** Map data from the specified array to the domain objects. + * @param data flat array holding the data to dispatch + */ + public void updateObjects(double[] data) { + for (Iterator iter = domainObjects.iterator(); iter.hasNext();) { + ArrayMapperEntry entry = (ArrayMapperEntry) iter.next(); + entry.object.mapStateFromArray(entry.offset, data); + } + } + + /** Map data from the domain objects to the internal array. + */ + public void updateArray() { + if (internalData == null) { + internalData = new double [size]; + } + updateArray(internalData); + } + + /** Map data from the domain objects to the specified array. + * @param data flat array where to put the data + */ + public void updateArray(double[] data) { + for (Iterator iter = domainObjects.iterator(); iter.hasNext();) { + ArrayMapperEntry entry = (ArrayMapperEntry) iter.next(); + entry.object.mapStateToArray(entry.offset, data); + } + } + + /** Container for all handled objects. */ + private ArrayList domainObjects; + + /** Total number of scalar elements handled. + * (size of the array) + */ + private int size; + + /** Flat array holding all data. + * This is null as long as nobody uses it (lazy creation) + */ + private double[] internalData; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/utilities/ArrayMapperEntry.java b/src/mantissa/src/org/spaceroots/mantissa/utilities/ArrayMapperEntry.java new file mode 100644 index 000000000..98ef8cfb1 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/utilities/ArrayMapperEntry.java @@ -0,0 +1,46 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.utilities; + +/** + * This class is a simple container for an offset and an + * {@link ArraySliceMappable} object. + + * @version $Id: ArrayMapperEntry.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +class ArrayMapperEntry { + + /** Mappable object. */ + public final ArraySliceMappable object; + + /** Offset from start of array. */ + public final int offset; + + /** Simple constructor. + * @param object mappable object + * @param offset offset from start of array + */ + public ArrayMapperEntry(ArraySliceMappable object, int offset) { + this.object = object; + this.offset = offset; + } + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/utilities/ArraySliceMappable.java b/src/mantissa/src/org/spaceroots/mantissa/utilities/ArraySliceMappable.java new file mode 100644 index 000000000..4db280c9a --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/utilities/ArraySliceMappable.java @@ -0,0 +1,81 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.utilities; + +/** This interface is used to map objects to and from simple arrays. + * + *

    Lots of mathematical algorithms are generic ones which can + * process the data from domain objects despite they ignore what + * this data represent. As an example, the same algorithm can + * integrate either the orbit evolution of a spacecraft under a + * specified force model or the electrical characteristics of a + * circuit after a switch is opened.

    + + *

    The approach of the Mantissa library is to define an interface + * for each such algorithm to represent the type of problem they can + * handle ({@link + * org.spaceroots.mantissa.ode.FirstOrderDifferentialEquations + * FirstOrderDifferentialEquations} for an ODE integrators, {@link + * org.spaceroots.mantissa.estimation.EstimationProblem + * EstimationProblem} for least squares estimators, ...). Furthermore, + * the state data that is handled by these algorithms is often a + * mixture of data coming from several domain objects (the orbit, + * plus the aerodynamical coefficients of the spacecraft, plus the + * characteristics of the thrusters, plus ...). Therefore, the user + * needs to gather and dispatch data between different objects + * representing different levels of abstraction.

    + + *

    This interface is designed to copy data back and forth between + * existing objects during the iterative processing of these + * algorithms and avoid the cost of recreating the objects.

    + + *

    The nominal way to use this interface is to have the domain + * objects implement it (either directly or using inheritance to add + * this feature to already existing objects) and to create one class + * that implements the problem interface (for example {@link + * org.spaceroots.mantissa.ode.FirstOrderDifferentialEquations}) and + * uses the {@link ArrayMapper} class to dispatch the data to and from + * the domain objects.

    + + * @see ArrayMapper + * + * @version $Id: ArraySliceMappable.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + * + */ + +public interface ArraySliceMappable { + + /** Get the dimension of the object. + * @return dimension of the object + */ + public int getStateDimension(); + + /** Reinitialize internal state from the specified array slice data. + * @param start start index in the array + * @param array array holding the data to extract + */ + public void mapStateFromArray(int start, double[] array); + + /** Store internal state data into the specified array slice. + * @param start start index in the array + * @param array array where data should be stored + */ + public void mapStateToArray(int start, double[] array); + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/utilities/Interval.java b/src/mantissa/src/org/spaceroots/mantissa/utilities/Interval.java new file mode 100644 index 000000000..c49e4bdfc --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/utilities/Interval.java @@ -0,0 +1,166 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.utilities; + +/** This class represents an interval on the real line. + + *

    This class allows to perform simple interval operations like + * point inclusion tests and intersection operations.

    + + *

    There is no distinction between open and closed intervals + * because real numbers cannot be represented exactly.

    + + * @see IntervalsList + * @author Luc Maisonobe + * @version $Id: Interval.java 1705 2006-09-17 19:57:39Z luc $ + */ +public class Interval { + + /** Build the [0, 0] interval. + */ + public Interval() { + inf = 0; + sup = 0; + } + + /** Build an interval with the given bounds. + *

    The given bounds do not need to be ordered, they will be + * reordered by the constructor.

    + * @param a first bound + * @param b second bound + */ + public Interval(double a, double b) { + if (a <= b) { + inf = a; + sup = b; + } else { + inf = b; + sup = a; + } + } + + /** Copy-constructor. + * @param i interval to copy + */ + public Interval(Interval i) { + inf = i.inf; + sup = i.sup; + } + + /** Get the lower bound of the interval. + * @return lower bound of the interval + */ + public double getInf() { + return inf; + } + + /** Get the upper bound of the interval. + * @return upper bound of the interval + */ + public double getSup() { + return sup; + } + + /** Get the length of the interval. + * @return length of the interval + */ + public double getLength() { + return sup - inf; + } + + /** Check if the interval contains a point. + * @param x point to check + * @return true if the interval contains x + */ + public boolean contains(double x) { + return (inf <= x) && (x <= sup); + } + + /** Check if the interval contains another interval. + * @param i interval to check + * @return true if i is completely included in the instance + */ + public boolean contains(Interval i) { + return (inf <= i.inf) && (i.sup <= sup); + } + + /** Check if an interval intersects the instance. + * @param i interval to check + * @return true if i intersects the instance + */ + public boolean intersects(Interval i) { + return (inf <= i.sup) && (i.inf <= sup); + } + + /** Add an interval to the instance. + *

    This method expands the instance.

    + *

    This operation is not a union operation. If + * the instance and the interval are disjoints (i.e. if {@link + * #intersects intersects(i)} would return false), then + * the hole between the intervals is filled in.

    + * @param i interval to add to the instance + */ + public void addToSelf(Interval i) { + inf = Math.min(inf, i.inf); + sup = Math.max(sup, i.sup); + } + + /** Add two intervals. + *

    This operation is not a union operation. If + * the intervals are disjoints (i.e. if {@link + * #intersects i1.intersects(i2)} would return false), then + * the hole between the intervals is filled in.

    + * @param i1 first interval + * @param i2 second interval + * @return a new interval + */ + public static Interval add(Interval i1, Interval i2) { + Interval copy = new Interval(i1); + copy.addToSelf(i2); + return copy; + } + + /** Intersects the instance with an interval. + *

    This method reduces the instance, it could even become empty + * if the interval does not intersects the instance.

    + * @param i interval with which the instance should be intersected + */ + public void intersectSelf(Interval i) { + inf = Math.max(inf, i.inf); + sup = Math.max(Math.min(sup, i.sup), inf); + } + + /** Intersect two intervals. + * @param i1 first interval + * @param i2 second interval + * @return a new interval which is the intersection of i1 with i2 + */ + public static Interval intersection(Interval i1, Interval i2) { + Interval copy = new Interval(i1); + copy.intersectSelf(i2); + return copy; + } + + /** Lower bound of the interval. */ + private double inf; + + /** Upper bound of the interval. */ + private double sup; + +} + diff --git a/src/mantissa/src/org/spaceroots/mantissa/utilities/IntervalsList.java b/src/mantissa/src/org/spaceroots/mantissa/utilities/IntervalsList.java new file mode 100644 index 000000000..72199d4a0 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/utilities/IntervalsList.java @@ -0,0 +1,362 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.utilities; + +import java.util.ArrayList; +import java.util.List; +import java.util.Iterator; + +/** This class represents an intervals list. + *

    An interval list represent a list of contiguous regions on the + * real line. All intervals of the list are disjoints to each other, + * they are stored in ascending order.

    + *

    The class supports the main set operations like union and + * intersection.

    + * @see Interval + * @author Luc Maisonobe + * @version $Id: IntervalsList.java 1705 2006-09-17 19:57:39Z luc $ + */ +public class IntervalsList { + + /** Build an empty intervals list. + */ + public IntervalsList() { + intervals = new ArrayList(); + } + + /** Build an intervals list containing only one interval. + * @param a first bound of the interval + * @param b second bound of the interval + */ + public IntervalsList(double a, double b) { + intervals = new ArrayList(); + intervals.add(new Interval(a, b)); + } + + /** Build an intervals list containing only one interval. + * @param i interval + */ + public IntervalsList(Interval i) { + intervals = new ArrayList(); + intervals.add(i); + } + + /** Build an intervals list containing two intervals. + * @param i1 first interval + * @param i2 second interval + */ + public IntervalsList(Interval i1, Interval i2) { + intervals = new ArrayList(); + if (i1.intersects(i2)) { + intervals.add(new Interval(Math.min(i1.getInf(), i2.getInf()), + Math.max(i1.getSup(), i2.getSup()))); + } else if (i1.getSup () < i2.getInf()) { + intervals.add(i1); + intervals.add(i2); + } else { + intervals.add(i2); + intervals.add(i1); + } + } + + /** Copy constructor. + *

    The copy operation is a deep copy: the underlying intervals + * are independant of the instances of the copied list.

    + * @param list intervals list to copy + */ + public IntervalsList(IntervalsList list) { + intervals = new ArrayList(list.intervals.size()); + for (Iterator iterator = list.intervals.iterator(); iterator.hasNext();) { + intervals.add(new Interval((Interval) iterator.next())); + } + } + + /** Check if the instance is empty. + * @return true if the instance is empty + */ + public boolean isEmpty() { + return intervals.isEmpty(); + } + + /** Check if the instance is connected. + *

    An interval list is connected if it contains only one + * interval.

    + * @return true is the instance is connected + */ + public boolean isConnex() { + return intervals.size() == 1; + } + + /** Get the lower bound of the list. + * @return lower bound of the list or Double.NaN if the list does + * not contain any interval + */ + public double getInf() { + return intervals.isEmpty() + ? Double.NaN : ((Interval) intervals.get(0)).getInf(); + } + + /** Get the upper bound of the list. + * @return upper bound of the list or Double.NaN if the list does + * not contain any interval + */ + public double getSup() { + return intervals.isEmpty() + ? Double.NaN : ((Interval) intervals.get(intervals.size() - 1)).getSup(); + } + + /** Get the number of intervals of the list. + * @return number of intervals in the list + */ + public int getSize() { + return intervals.size(); + } + + /** Get an interval from the list. + * @param i index of the interval + * @return interval at index i + */ + public Interval getInterval(int i) { + return (Interval) intervals.get(i); + } + + /** Get the ordered list of disjoints intervals. + * @return list of disjoints intervals in ascending order + */ + public List getIntervals() { + return intervals; + } + + /** Check if the list contains a point. + * @param x point to check + * @return true if the list contains x + */ + public boolean contains(double x) { + for (Iterator iterator = intervals.iterator(); iterator.hasNext();) { + if (((Interval) iterator.next()).contains(x)) { + return true; + } + } + return false; + } + + /** Check if the list contains an interval. + * @param i interval to check + * @return true if i is completely included in the instance + */ + public boolean contains(Interval i) { + for (Iterator iterator = intervals.iterator(); iterator.hasNext();) { + if (((Interval) iterator.next()).contains(i)) { + return true; + } + } + return false; + } + + /** Check if an interval intersects the instance. + * @param i interval to check + * @return true if i intersects the instance + */ + public boolean intersects(Interval i) { + for (Iterator iterator = intervals.iterator(); iterator.hasNext();) { + if (((Interval) iterator.next()).intersects(i)) { + return true; + } + } + return false; + } + + /** Add an interval to the instance. + *

    This method expands the instance.

    + *

    This operation is a union operation. The number of intervals + * in the list can decrease if the interval fills some holes between + * existing intervals in the list.

    + * @param i interval to add to the instance + */ + public void addToSelf(Interval i) { + + List newIntervals = new ArrayList(); + double inf = Double.NaN; + double sup = Double.NaN; + boolean pending = false; + boolean processed = false; + for (Iterator iterator = intervals.iterator(); iterator.hasNext();) { + Interval local = (Interval) iterator.next(); + + if (local.getSup() < i.getInf()) { + newIntervals.add(local); + } else if (local.getInf() < i.getSup()) { + if (! pending) { + inf = Math.min(local.getInf(), i.getInf()); + pending = true; + processed = true; + } + sup = Math.max(local.getSup(), i.getSup()); + } else { + if (pending) { + newIntervals.add(new Interval(inf, sup)); + pending = false; + } else if (! processed) { + newIntervals.add(i); + } + processed = true; + newIntervals.add(local); + } + } + + if (pending) { + newIntervals.add(new Interval(inf, sup)); + } else if (! processed) { + newIntervals.add(i); + } + + intervals = newIntervals; + + } + + /** Add an intervals list and an interval. + * @param list intervals list + * @param i interval + * @return a new intervals list which is the union of list and i + */ + public static IntervalsList add(IntervalsList list, Interval i) { + IntervalsList copy = new IntervalsList(list); + copy.addToSelf(i); + return copy; + } + + /** Remove an interval from the list. + *

    This method reduces the instance. This operation is defined in + * terms of points set operation. As an example, if the [2, 3] + * interval is subtracted from the list containing only the [0, 10] + * interval, the result will be the [0, 2] U [3, 10] intervals + * list.

    + * @param i interval to remove + */ + public void subtractFromSelf(Interval i) { + double a = Math.min(getInf(), i.getInf()); + double b = Math.max(getSup(), i.getSup()); + intersectSelf(new IntervalsList(new Interval(a - 1.0, i.getInf()), + new Interval(i.getSup(), b + 1.0))); + } + + /** Remove an interval from a list. + * @param list intervals list + * @param i interval to remove + * @return a new intervals list + */ + public static IntervalsList subtract(IntervalsList list, Interval i) { + IntervalsList copy = new IntervalsList(list); + copy.subtractFromSelf(i); + return copy; + } + + /** Intersects the instance and an interval. + * @param i interval + */ + public void intersectSelf(Interval i) { + List newIntervals = new ArrayList(); + for (Iterator iterator = intervals.iterator(); iterator.hasNext();) { + Interval local = (Interval) iterator.next(); + if (local.intersects(i)) { + newIntervals.add(Interval.intersection(local, i)); + } + } + intervals = newIntervals; + } + + /** Intersect a list and an interval. + * @param list intervals list + * @param i interval + * @return the intersection of list and i + */ + public static IntervalsList intersection(IntervalsList list, Interval i) { + IntervalsList copy = new IntervalsList(list); + copy.intersectSelf(i); + return copy; + } + + /** Add an intervals list to the instance. + *

    This method expands the instance.

    + *

    This operation is a union operation. The number of intervals + * in the list can decrease if the list fills some holes between + * existing intervals in the instance.

    + * @param list intervals list to add to the instance + */ + public void addToSelf(IntervalsList list) { + for (Iterator iterator = list.intervals.iterator(); iterator.hasNext();) { + addToSelf((Interval) iterator.next()); + } + } + + /** Add two intervals lists. + * @param list1 first intervals list + * @param list2 second intervals list + * @return a new intervals list which is the union of list1 and list2 + */ + public static IntervalsList add(IntervalsList list1, IntervalsList list2) { + IntervalsList copy = new IntervalsList(list1); + copy.addToSelf(list2); + return copy; + } + + /** Remove an intervals list from the instance. + * @param list intervals list to remove + */ + public void subtractFromSelf(IntervalsList list) { + for (Iterator iterator = list.intervals.iterator(); iterator.hasNext();) { + subtractFromSelf((Interval) iterator.next()); + } + } + + /** Remove an intervals list from another one. + * @param list1 intervals list + * @param list2 intervals list to remove + * @return a new intervals list + */ + public static IntervalsList subtract(IntervalsList list1, IntervalsList list2) { + IntervalsList copy = new IntervalsList(list1); + copy.subtractFromSelf(list2); + return copy; + } + + /** Intersect the instance and another intervals list. + * @param list list to intersect with the instance + */ + public void intersectSelf(IntervalsList list) { + intervals = intersection(this, list).intervals; + } + + /** Intersect two intervals lists. + * @param list1 first intervals list + * @param list2 second intervals list + * @return a new list which is the intersection of list1 and list2 + */ + public static IntervalsList intersection(IntervalsList list1, IntervalsList list2) { + IntervalsList list = new IntervalsList(); + for (Iterator iterator = list2.intervals.iterator(); iterator.hasNext();) { + list.addToSelf(intersection(list1, (Interval) iterator.next())); + } + return list; + } + + /** The list of intervals. */ + private List intervals; + +} + diff --git a/src/mantissa/src/org/spaceroots/mantissa/utilities/MappableArray.java b/src/mantissa/src/org/spaceroots/mantissa/utilities/MappableArray.java new file mode 100644 index 000000000..799a1d892 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/utilities/MappableArray.java @@ -0,0 +1,93 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.utilities; + +/** + * Wrapper class around an array in order to have it implement the + * {@link ArraySliceMappable} interface. + + * @version $Id: MappableArray.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class MappableArray + implements ArraySliceMappable { + + /** Simple constructor. + * Build a mappable array from its dimension + * @param dimension dimension of the array + */ + public MappableArray(int dimension) { + internalArray = new double[dimension]; + for (int i = 0; i < dimension; ++i) { + internalArray[i] = 0; + } + } + + /** Simple constructor. + * Build a mappable array from an existing array + * @param array array to use + * @param doReallocate true if a new array should be allocated and + * initialized using the other argument, false if the instance + * should reference the existing array throughout its lifetime + */ + public MappableArray(double[] array, boolean doReallocate) { + if (doReallocate) { + internalArray = new double[array.length]; + System.arraycopy(array, 0, internalArray, 0, array.length); + } else { + internalArray = array; + } + } + + /** Get the array stored in the instance. + * @return array stored in the instance + */ + public double[] getArray () { + return internalArray; + } + + /** Get the dimension of the internal array. + * @return dimension of the array + */ + public int getStateDimension() { + return internalArray.length; + } + + /** Reinitialize internal state from the specified array slice data. + * @param start start index in the array + * @param array array holding the data to extract + */ + public void mapStateFromArray(int start, double[] array) { + System.arraycopy(array, start, internalArray, 0, internalArray.length); + } + + /** Store internal state data into the specified array slice. + * @param start start index in the array + * @param array array where data should be stored + */ + public void mapStateToArray(int start, double[] array) { + System.arraycopy(internalArray, 0, array, start, internalArray.length); + } + + /** Internal array holding all data. + */ + double[] internalArray; + +} diff --git a/src/mantissa/src/org/spaceroots/mantissa/utilities/MappableScalar.java b/src/mantissa/src/org/spaceroots/mantissa/utilities/MappableScalar.java new file mode 100644 index 000000000..9b5ac4f24 --- /dev/null +++ b/src/mantissa/src/org/spaceroots/mantissa/utilities/MappableScalar.java @@ -0,0 +1,88 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.utilities; + +/** + * Wrapper class around a scalar in order to have it implement the + * {@link ArraySliceMappable} interface. + + * @version $Id: MappableScalar.java 1705 2006-09-17 19:57:39Z luc $ + * @author L. Maisonobe + + */ + +public class MappableScalar + implements ArraySliceMappable { + + /** Simple constructor. + * Build a mappable scalar + */ + public MappableScalar() { + value = 0; + } + + /** Simple constructor. + * Build a mappable scalar from its initial value + * @param value initial value of the scalar + */ + public MappableScalar(double value) { + this.value = value; + } + + /** Get the value stored in the instance. + * @return value stored in the instance + */ + public double getValue () { + return value; + } + + /** Set the value stored in the instance. + * @param value value to store in the instance + */ + public void setValue (double value) { + this.value = value; + } + + /** Get the dimension of the internal array. + * @return dimension of the array (always 1 for this class) + */ + public int getStateDimension() { + return 1; + } + + /** Reinitialize internal state from the specified array slice data. + * @param start start index in the array + * @param array array holding the data to extract + */ + public void mapStateFromArray(int start, double[] array) { + value = array[start]; + } + + /** Store internal state data into the specified array slice. + * @param start start index in the array + * @param array array where data should be stored + */ + public void mapStateToArray(int start, double[] array) { + array[start] = value; + } + + /** Internal scalar. + */ + double value; + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/AllTests.java b/src/mantissa/tests-src/org/spaceroots/mantissa/AllTests.java new file mode 100644 index 000000000..ce68f7336 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/AllTests.java @@ -0,0 +1,45 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class AllTests { + + public static Test suite() { + + TestSuite suite= new TestSuite("org.spaceroots.mantissa"); + + suite.addTest(org.spaceroots.mantissa.linalg.AllTests.suite()); + suite.addTest(org.spaceroots.mantissa.estimation.AllTests.suite()); + suite.addTest(org.spaceroots.mantissa.functions.AllTests.suite()); + suite.addTest(org.spaceroots.mantissa.roots.AllTests.suite()); + suite.addTest(org.spaceroots.mantissa.fitting.AllTests.suite()); + suite.addTest(org.spaceroots.mantissa.ode.AllTests.suite()); + suite.addTest(org.spaceroots.mantissa.quadrature.AllTests.suite()); + suite.addTest(org.spaceroots.mantissa.utilities.AllTests.suite()); + suite.addTest(org.spaceroots.mantissa.geometry.AllTests.suite()); + suite.addTest(org.spaceroots.mantissa.algebra.AllTests.suite()); + suite.addTest(org.spaceroots.mantissa.optimization.AllTests.suite()); + + return suite; + + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/algebra/AllTests.java b/src/mantissa/tests-src/org/spaceroots/mantissa/algebra/AllTests.java new file mode 100644 index 000000000..a840155f3 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/algebra/AllTests.java @@ -0,0 +1,40 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.algebra; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class AllTests { + public static Test suite() { + + TestSuite suite = new TestSuite("org.spaceroots.mantissa.algebra"); + + suite.addTest(RationalNumberTest.suite()); + suite.addTest(PolynomialRationalTest.suite()); + suite.addTest(PolynomialDoubleTest.suite()); + suite.addTest(ChebyshevTest.suite()); + suite.addTest(HermiteTest.suite()); + suite.addTest(LegendreTest.suite()); + suite.addTest(LaguerreTest.suite()); + suite.addTest(PolynomialFractionTest.suite()); + + return suite; + + } +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/algebra/ChebyshevTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/algebra/ChebyshevTest.java new file mode 100755 index 000000000..d4a4ccb51 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/algebra/ChebyshevTest.java @@ -0,0 +1,86 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.algebra; + +import junit.framework.*; + +public class ChebyshevTest + extends TestCase { + + public ChebyshevTest(String name) { + super(name); + } + + public void aatestOne() { + assertTrue(new Chebyshev().isOne()); + } + + public void testFirstPolynomials() { + + checkPolynomial(new Chebyshev(3), "-3 x + 4 x^3"); + checkPolynomial(new Chebyshev(2), "-1 + 2 x^2"); + checkPolynomial(new Chebyshev(1), "x"); + checkPolynomial(new Chebyshev(0), "1"); + + checkPolynomial(new Chebyshev(7), "-7 x + 56 x^3 - 112 x^5 + 64 x^7"); + checkPolynomial(new Chebyshev(6), "-1 + 18 x^2 - 48 x^4 + 32 x^6"); + checkPolynomial(new Chebyshev(5), "5 x - 20 x^3 + 16 x^5"); + checkPolynomial(new Chebyshev(4), "1 - 8 x^2 + 8 x^4"); + + } + + public void aatestBounds() { + for (int k = 0; k < 12; ++k) { + Chebyshev Tk = new Chebyshev(k); + for (double x = -1.0; x <= 1.0; x += 0.02) { + assertTrue(Math.abs(Tk.valueAt(x)) < (1.0 + 1.0e-12)); + } + } + } + + public void aatestDifferentials() { + for (int k = 0; k < 12; ++k) { + + Polynomial.Rational Tk0 = new Chebyshev(k); + Polynomial.Rational Tk1 = (Polynomial.Rational) Tk0.getDerivative(); + Polynomial.Rational Tk2 = (Polynomial.Rational) Tk1.getDerivative(); + + Polynomial.Rational g0 = new Polynomial.Rational(k * k); + Polynomial.Rational g1 = new Polynomial.Rational(-1l, 0l); + Polynomial.Rational g2 = new Polynomial.Rational(-1l, 0l, 1l); + + Polynomial.Rational Tk0g0 = Polynomial.Rational.multiply(Tk0, g0); + Polynomial.Rational Tk1g1 = Polynomial.Rational.multiply(Tk1, g1); + Polynomial.Rational Tk2g2 = Polynomial.Rational.multiply(Tk2, g2); + + Polynomial.Rational d = + Polynomial.Rational.add(Tk0g0, Polynomial.Rational.add(Tk1g1, Tk2g2)); + assertTrue(d.isZero()); + + } + } + + public void checkPolynomial(Polynomial.Rational p, String reference) { + assertTrue(p.toString().equals(reference)); + } + + public static Test suite() { + return new TestSuite(ChebyshevTest.class); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/algebra/HermiteTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/algebra/HermiteTest.java new file mode 100755 index 000000000..028a3c13e --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/algebra/HermiteTest.java @@ -0,0 +1,77 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.algebra; + +import junit.framework.*; + +public class HermiteTest + extends TestCase { + + public HermiteTest(String name) { + super(name); + } + + public void testOne() { + assertTrue(new Hermite().isOne()); + } + + public void testFirstPolynomials() { + + checkPolynomial(new Hermite(3), "-12 x + 8 x^3"); + checkPolynomial(new Hermite(2), "-2 + 4 x^2"); + checkPolynomial(new Hermite(1), "2 x"); + checkPolynomial(new Hermite(0), "1"); + + checkPolynomial(new Hermite(7), "-1680 x + 3360 x^3 - 1344 x^5 + 128 x^7"); + checkPolynomial(new Hermite(6), "-120 + 720 x^2 - 480 x^4 + 64 x^6"); + checkPolynomial(new Hermite(5), "120 x - 160 x^3 + 32 x^5"); + checkPolynomial(new Hermite(4), "12 - 48 x^2 + 16 x^4"); + + } + + public void testDifferentials() { + for (int k = 0; k < 12; ++k) { + + Polynomial.Rational Hk0 = new Hermite(k); + Polynomial.Rational Hk1 = (Polynomial.Rational) Hk0.getDerivative(); + Polynomial.Rational Hk2 = (Polynomial.Rational) Hk1.getDerivative(); + + Polynomial.Rational g0 = new Polynomial.Rational(2l * k); + Polynomial.Rational g1 = new Polynomial.Rational(-2l, 0l); + Polynomial.Rational g2 = new Polynomial.Rational(1l); + + Polynomial.Rational Hk0g0 = Polynomial.Rational.multiply(Hk0, g0); + Polynomial.Rational Hk1g1 = Polynomial.Rational.multiply(Hk1, g1); + Polynomial.Rational Hk2g2 = Polynomial.Rational.multiply(Hk2, g2); + + Polynomial.Rational d = + Polynomial.Rational.add(Hk0g0, Polynomial.Rational.add(Hk1g1, Hk2g2)); + assertTrue(d.isZero()); + + } + } + + public void checkPolynomial(Polynomial.Rational p, String reference) { + assertTrue(p.toString().equals(reference)); + } + + public static Test suite() { + return new TestSuite(HermiteTest.class); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/algebra/LaguerreTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/algebra/LaguerreTest.java new file mode 100644 index 000000000..cd043374e --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/algebra/LaguerreTest.java @@ -0,0 +1,83 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.algebra; + +import junit.framework.*; + +public class LaguerreTest + extends TestCase { + + public LaguerreTest(String name) { + super(name); + } + + public void testOne() { + assertTrue(new Laguerre().isOne()); + } + + public void testFirstPolynomials() { + + checkLaguerre(new Laguerre(3), 6l, "6 - 18 x + 9 x^2 - x^3"); + checkLaguerre(new Laguerre(2), 2l, "2 - 4 x + x^2"); + checkLaguerre(new Laguerre(1), 1l, "1 - x"); + checkLaguerre(new Laguerre(0), 1l, "1"); + + checkLaguerre(new Laguerre(7), 5040l, + "5040 - 35280 x + 52920 x^2 - 29400 x^3" + + " + 7350 x^4 - 882 x^5 + 49 x^6 - x^7"); + checkLaguerre(new Laguerre(6), 720l, + "720 - 4320 x + 5400 x^2 - 2400 x^3 + 450 x^4" + + " - 36 x^5 + x^6"); + checkLaguerre(new Laguerre(5), 120l, + "120 - 600 x + 600 x^2 - 200 x^3 + 25 x^4 - x^5"); + checkLaguerre(new Laguerre(4), 24l, + "24 - 96 x + 72 x^2 - 16 x^3 + x^4"); + + } + + public void testDifferentials() { + for (int k = 0; k < 12; ++k) { + + Polynomial.Rational Lk0 = new Laguerre(k); + Polynomial.Rational Lk1 = (Polynomial.Rational) Lk0.getDerivative(); + Polynomial.Rational Lk2 = (Polynomial.Rational) Lk1.getDerivative(); + + Polynomial.Rational g0 = new Polynomial.Rational(k); + Polynomial.Rational g1 = new Polynomial.Rational(-1l, 1l); + Polynomial.Rational g2 = new Polynomial.Rational(1l, 0l); + + Polynomial.Rational Lk0g0 = Polynomial.Rational.multiply(Lk0, g0); + Polynomial.Rational Lk1g1 = Polynomial.Rational.multiply(Lk1, g1); + Polynomial.Rational Lk2g2 = Polynomial.Rational.multiply(Lk2, g2); + + Polynomial.Rational d = + Polynomial.Rational.add(Lk0g0, Polynomial.Rational.add(Lk1g1, Lk2g2)); + assertTrue(d.isZero()); + + } + } + + public void checkLaguerre(Laguerre p, long denominator, String reference) { + assertTrue(Laguerre.multiply(p, denominator).toString().equals(reference)); + } + + public static Test suite() { + return new TestSuite(LaguerreTest.class); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/algebra/LegendreTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/algebra/LegendreTest.java new file mode 100755 index 000000000..63f7b3bd2 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/algebra/LegendreTest.java @@ -0,0 +1,102 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.algebra; + +import junit.framework.*; + +public class LegendreTest + extends TestCase { + + public LegendreTest(String name) { + super(name); + } + + public void testOne() { + assertTrue(new Legendre().isOne()); + } + + public void testFirstPolynomials() { + + checkLegendre(new Legendre(3), 2l, "-3 x + 5 x^3"); + checkLegendre(new Legendre(2), 2l, "-1 + 3 x^2"); + checkLegendre(new Legendre(1), 1l, "x"); + checkLegendre(new Legendre(0), 1l, "1"); + + checkLegendre(new Legendre(7), 16l, "-35 x + 315 x^3 - 693 x^5 + 429 x^7"); + checkLegendre(new Legendre(6), 16l, "-5 + 105 x^2 - 315 x^4 + 231 x^6"); + checkLegendre(new Legendre(5), 8l, "15 x - 70 x^3 + 63 x^5"); + checkLegendre(new Legendre(4), 8l, "3 - 30 x^2 + 35 x^4"); + + } + + public void testDifferentials() { + for (int k = 0; k < 12; ++k) { + + Polynomial.Rational Pk0 = new Legendre(k); + Polynomial.Rational Pk1 = (Polynomial.Rational) Pk0.getDerivative(); + Polynomial.Rational Pk2 = (Polynomial.Rational) Pk1.getDerivative(); + + Polynomial.Rational g0 = new Polynomial.Rational(k * (k + 1)); + Polynomial.Rational g1 = new Polynomial.Rational(-2l, 0l); + Polynomial.Rational g2 = new Polynomial.Rational(-1l, 0l, 1l); + + Polynomial.Rational Pk0g0 = Polynomial.Rational.multiply(Pk0, g0); + Polynomial.Rational Pk1g1 = Polynomial.Rational.multiply(Pk1, g1); + Polynomial.Rational Pk2g2 = Polynomial.Rational.multiply(Pk2, g2); + + Polynomial.Rational d = + Polynomial.Rational.add(Pk0g0, Polynomial.Rational.add(Pk1g1, Pk2g2)); + assertTrue(d.isZero()); + + } + } + + public void testHighDegree() { + checkLegendre(new Legendre(40), 274877906944l, + "34461632205" + + " - 28258538408100 x^2" + + " + 3847870979902950 x^4" + + " - 207785032914759300 x^6" + + " + 5929294332103310025 x^8" + + " - 103301483474866556880 x^10" + + " + 1197358103913226000200 x^12" + + " - 9763073770369381232400 x^14" + + " + 58171647881784229843050 x^16" + + " - 260061484647976556945400 x^18" + + " + 888315281771246239250340 x^20" + + " - 2345767627188139419665400 x^22" + + " + 4819022625419112503443050 x^24" + + " - 7710436200670580005508880 x^26" + + " + 9566652323054238154983240 x^28" + + " - 9104813935044723209570256 x^30" + + " + 6516550296251767619752905 x^32" + + " - 3391858621221953912598660 x^34" + + " + 1211378079007840683070950 x^36" + + " - 265365894974690562152100 x^38" + + " + 26876802183334044115405 x^40"); + } + + public void checkLegendre(Legendre p, long denominator, String reference) { + assertTrue(Legendre.multiply(p, denominator).toString().equals(reference)); + } + + public static Test suite() { + return new TestSuite(LegendreTest.class); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/algebra/PolynomialDoubleTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/algebra/PolynomialDoubleTest.java new file mode 100755 index 000000000..d92c47c39 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/algebra/PolynomialDoubleTest.java @@ -0,0 +1,139 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.algebra; + +import junit.framework.*; + +public class PolynomialDoubleTest + extends TestCase { + + public PolynomialDoubleTest(String name) { + super(name); + } + + public void testConstructors() { + + Polynomial.Double p = new Polynomial.Double(1.0, 3.0, -5.0); + double[] a = p.getCoefficients(); + assertEquals(a.length, 3); + assertEquals(-5.0, a[0], 1.0e-12); + assertEquals(3.0, a[1], 1.0e-12); + assertEquals(1.0, a[2], 1.0e-12); + assertEquals(p.getDegree(), 2); + + assertEquals(1, new Polynomial.Double(0.0, 3.0, 5.0).getDegree()); + assertEquals(0, new Polynomial.Double(0.0, 0.0, 5.0).getDegree()); + assertEquals(0, new Polynomial.Double(0.0, 0.0, 0.0).getDegree()); + assertEquals(1, new Polynomial.Double(3.0, 5.0).getDegree()); + assertEquals(0, new Polynomial.Double(0.0, 5.0).getDegree()); + assertEquals(0, new Polynomial.Double(0.0, 0.0).getDegree()); + assertEquals(0, new Polynomial.Double(5.0).getDegree()); + assertEquals(0, new Polynomial.Double(0.0).getDegree()); + + } + + public void testConversion() { + Polynomial.Rational r = new Polynomial.Rational(1l, 3l, -5l); + r.multiplySelf(new RationalNumber(1l, 2l)); + Polynomial.Double p = new Polynomial.Double(r); + checkPolynomial(p, "-2.5 + 1.5 x + 0.5 x^2"); + } + + public void testString() { + + Polynomial.Double p = new Polynomial.Double(1.0, 3.0, -5.0); + checkPolynomial(p, "-5.0 + 3.0 x + x^2"); + p.setUnknownName("alpha"); + checkPolynomial(p, "-5.0 + 3.0 alpha + alpha^2"); + p.setUnknownName(null); + checkPolynomial(p, "-5.0 + 3.0 x + x^2"); + + checkPolynomial(new Polynomial.Double(3.0, -2.0, 0.0), + "-2.0 x + 3.0 x^2"); + checkPolynomial(new Polynomial.Double(3.0, -2.0, 1.0), + "1.0 - 2.0 x + 3.0 x^2"); + checkPolynomial(new Polynomial.Double(3.0, 2.0, 0.0), + "2.0 x + 3.0 x^2"); + checkPolynomial(new Polynomial.Double(3.0, 2.0, 1.0), + "1.0 + 2.0 x + 3.0 x^2"); + checkPolynomial(new Polynomial.Double(3.0, 0.0, 1.0), + "1.0 + 3.0 x^2"); + checkPolynomial(new Polynomial.Double(0.0), + "0"); + + } + + public void testAddition() { + + Polynomial.Double p1 = new Polynomial.Double(1.0, -2.0); + Polynomial.Double p2 = new Polynomial.Double(0.0, -1.0, 2.0); + assertTrue(Polynomial.Double.add(p1, p2).isZero()); + + p2 = new Polynomial.Double(p1); + p2.addToSelf(p2); + checkPolynomial(p2, "-4.0 + 2.0 x"); + + p1 = new Polynomial.Double(2.0, -4.0, 1.0); + p2 = new Polynomial.Double(-2.0, 3.0, -1.0); + p1.addToSelf(p2); + assertEquals(1, p1.getDegree()); + checkPolynomial(p1, "-x"); + + } + + public void testSubtraction() { + + Polynomial.Double p1 = new Polynomial.Double(1.0, -2.0); + assertTrue(Polynomial.Double.subtract(p1, p1).isZero()); + + Polynomial.Double p2 = new Polynomial.Double(6.0, -2.0); + p2.subtractFromSelf(p1); + checkPolynomial(p2, "5.0 x"); + + p1 = new Polynomial.Double(2.0, -4.0, 1.0); + p2 = new Polynomial.Double(2.0, 3.0, -1.0); + p1.subtractFromSelf(p2); + assertEquals(1, p1.getDegree()); + checkPolynomial(p1, "2.0 - 7.0 x"); + + } + + public void testMultiplication() { + + Polynomial.Double p1 = new Polynomial.Double(2.0, -3.0); + Polynomial.Double p2 = new Polynomial.Double(1.0, 2.0, 3.0); + checkPolynomial(Polynomial.Double.multiply(p1, p2), "-9.0 + x^2 + 2.0 x^3"); + + p1 = new Polynomial.Double(1.0, 0.0); + p2 = new Polynomial.Double(p1); + for (int i = 2; i < 10; ++i) { + p2.multiplySelf(p1); + checkPolynomial(p2, "x^" + i); + } + + } + + public void checkPolynomial(Polynomial.Double p, String reference) { + assertEquals(reference, p.toString()); + } + + public static Test suite() { + return new TestSuite(PolynomialDoubleTest.class); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/algebra/PolynomialFractionTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/algebra/PolynomialFractionTest.java new file mode 100755 index 000000000..763c3e7bc --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/algebra/PolynomialFractionTest.java @@ -0,0 +1,223 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.algebra; + +import junit.framework.*; + +public class PolynomialFractionTest + extends TestCase { + + public PolynomialFractionTest(String name) { + super(name); + } + + public void testNullDenominator() { + try { + new PolynomialFraction(1l, 0l); + fail("an exception should have been thrown"); + } catch (ArithmeticException e) { + } catch (Exception e) { + fail("wrong exception caught"); + } + } + + public void testToString() { + checkValue(new PolynomialFraction(1l, 2l), "1/2"); + checkValue(new PolynomialFraction(-1l, 2l), "-1/2"); + checkValue(new PolynomialFraction(1l, -2l), "-1/2"); + checkValue(new PolynomialFraction(-1l, -2l), "1/2"); + checkValue(new PolynomialFraction(0l, 500l), "0"); + checkValue(new PolynomialFraction(-12l), "-12"); + checkValue(new PolynomialFraction(12l), "12"); + } + + public void testSimplification() { + checkValue(new PolynomialFraction(2l, 4l), "1/2"); + checkValue(new PolynomialFraction(307692l, 999999l), "4/13"); + checkValue(new PolynomialFraction(999999l, 307692l), "13/4"); + } + + public void testInvert() { + + PolynomialFraction f = new PolynomialFraction(2l, 4l); + f.invertSelf(); + checkValue(f, "2"); + f.invertSelf(); + checkValue(f, "1/2"); + + f = new PolynomialFraction(120l); + f.invertSelf(); + checkValue(f, "1/120"); + + f = new PolynomialFraction(0l, 4l); + try { + f.invertSelf(); + fail("an exception should have been thrown"); + } catch (ArithmeticException e) { + } catch (Exception e) { + fail("wrong exception caught"); + } + + f = new PolynomialFraction(307692l, 999999l); + PolynomialFraction fInverse = PolynomialFraction.invert(f); + checkValue(fInverse, "13/4"); + checkValue(f, "4/13"); + + } + + public void testAddition() { + + PolynomialFraction f1 = new PolynomialFraction(4l, 6l); + f1.addToSelf(f1); + checkValue(f1, "4/3"); + + checkValue(PolynomialFraction.add(new PolynomialFraction(17l, 3l), + new PolynomialFraction(-17l, 3l)), + "0"); + checkValue(PolynomialFraction.add(new PolynomialFraction(2l, 3l), + new PolynomialFraction(3l, 4l)), + "17/12"); + checkValue(PolynomialFraction.add(new PolynomialFraction(1l, 6l), + new PolynomialFraction(2l, 6l)), + "1/2"); + checkValue(PolynomialFraction.add(new PolynomialFraction(4l, 5l), + new PolynomialFraction(-3l, 4l)), + "1/20"); + checkValue(PolynomialFraction.add(new PolynomialFraction(-3l, 4l), + new PolynomialFraction(4l, 5l)), + "1/20"); + + } + + public void testSubtraction() { + + PolynomialFraction f1 = new PolynomialFraction(4l, 6l); + f1.subtractFromSelf(f1); + checkValue(f1, "0"); + + checkValue(PolynomialFraction.subtract(new PolynomialFraction(7l, 3l), + new PolynomialFraction(-7l, 3l)), + "14/3"); + + checkValue(PolynomialFraction.subtract(new PolynomialFraction(3l, 4l), + new PolynomialFraction(2l, 3l)), + "1/12"); + checkValue(PolynomialFraction.subtract(new PolynomialFraction(3l, 4l), + new PolynomialFraction(-2l, 3l)), + "17/12"); + checkValue(PolynomialFraction.subtract(new PolynomialFraction(-3l, 4l), + new PolynomialFraction(2l, 3l)), + "-17/12"); + checkValue(PolynomialFraction.subtract(new PolynomialFraction(-3l, 4l), + new PolynomialFraction(-2l, 3l)), + "-1/12"); + + checkValue(PolynomialFraction.subtract(new PolynomialFraction(2l, 3l), + new PolynomialFraction(3l, 4l)), + "-1/12"); + checkValue(PolynomialFraction.subtract(new PolynomialFraction(-2l, 3l), + new PolynomialFraction(3l, 4l)), + "-17/12"); + checkValue(PolynomialFraction.subtract(new PolynomialFraction(2l, 3l), + new PolynomialFraction(-3l, 4l)), + "17/12"); + checkValue(PolynomialFraction.subtract(new PolynomialFraction(-2l, 3l), + new PolynomialFraction(-3l, 4l)), + "1/12"); + + checkValue(PolynomialFraction.subtract(new PolynomialFraction(1l, 6l), + new PolynomialFraction(2l, 6l)), + "-1/6"); + checkValue(PolynomialFraction.subtract(new PolynomialFraction(1l, 2l), + new PolynomialFraction(1l, 6l)), + "1/3"); + + } + + public void testMultiplication() { + + PolynomialFraction f = new PolynomialFraction(2l, 3l); + f.multiplySelf(new PolynomialFraction(9l,4l)); + checkValue(f, "3/2"); + + checkValue(PolynomialFraction.multiply(new PolynomialFraction(1l, 2l), + new PolynomialFraction(0l)), + "0"); + checkValue(PolynomialFraction.multiply(new PolynomialFraction(4l, 15l), + new PolynomialFraction(-5l, 2l)), + "-2/3"); + checkValue(PolynomialFraction.multiply(new PolynomialFraction(-4l, 15l), + new PolynomialFraction(5l, 2l)), + "-2/3"); + checkValue(PolynomialFraction.multiply(new PolynomialFraction(4l, 15l), + new PolynomialFraction(5l, 2l)), + "2/3"); + checkValue(PolynomialFraction.multiply(new PolynomialFraction(-4l, 15l), + new PolynomialFraction(-5l, 2l)), + "2/3"); + + } + + public void testDivision() { + + PolynomialFraction f = new PolynomialFraction(2l, 3l); + f.divideSelf(new PolynomialFraction(4l,9l)); + checkValue(f, "3/2"); + + try { + PolynomialFraction.divide(new PolynomialFraction(1l, 2l), + new PolynomialFraction(0l)); + fail("an exception should have been thrown"); + } catch (ArithmeticException e) { + } catch (Exception e) { + fail("wrong exception caught"); + } + + checkValue(PolynomialFraction.divide(new PolynomialFraction(4l, 15l), + new PolynomialFraction(-2l, 5l)), + "-2/3"); + checkValue(PolynomialFraction.divide(new PolynomialFraction(-4l, 15l), + new PolynomialFraction(2l, 5l)), + "-2/3"); + checkValue(PolynomialFraction.divide(new PolynomialFraction(4l, 15l), + new PolynomialFraction(2l, 5l)), + "2/3"); + checkValue(PolynomialFraction.divide(new PolynomialFraction(-4l, 15l), + new PolynomialFraction(-2l, 5l)), + "2/3"); + + } + + public void testEuclidianDivision() { + checkValue(new PolynomialFraction(new Polynomial.Rational(1l, 0l, -1l), + new Polynomial.Rational(2l, 2l)), + "-1/2 + 1/2 x"); + checkValue(new PolynomialFraction(new Polynomial.Rational(1l, 3l, 2l), + new Polynomial.Rational(2l, 10l, 12l)), + "(1 + x)/(6 + 2 x)"); + } + + private void checkValue(PolynomialFraction f, String reference) { + assertTrue(f.toString().equals(reference)); + } + + public static Test suite() { + return new TestSuite(PolynomialFractionTest.class); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/algebra/PolynomialRationalTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/algebra/PolynomialRationalTest.java new file mode 100755 index 000000000..f633296ec --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/algebra/PolynomialRationalTest.java @@ -0,0 +1,151 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.algebra; + +import java.math.BigInteger; + +import junit.framework.*; + +public class PolynomialRationalTest + extends TestCase { + + public PolynomialRationalTest(String name) { + super(name); + } + + public void testZero() { + assertTrue(new Polynomial.Rational().isZero()); + } + + public void testConstructors() { + + Polynomial.Rational p = new Polynomial.Rational(1l, 3l, -5l); + RationalNumber[] a = p.getCoefficients(); + assertEquals(a.length, 3); + assertEquals(new RationalNumber(-5l), a[0]); + assertEquals(new RationalNumber(3l), a[1]); + assertEquals(new RationalNumber(1l), a[2]); + assertEquals(2, p.getDegree()); + + assertEquals(1, new Polynomial.Rational(0l, 3l, 5l).getDegree()); + assertEquals(0, new Polynomial.Rational(0l, 0l, 5l).getDegree()); + assertEquals(0, new Polynomial.Rational(0l, 0l, 0l).getDegree()); + assertEquals(1, new Polynomial.Rational(3l, 5l).getDegree()); + assertEquals(0, new Polynomial.Rational(0l, 5l).getDegree()); + assertEquals(0, new Polynomial.Rational(0l, 0l).getDegree()); + assertEquals(0, new Polynomial.Rational(5l).getDegree()); + assertEquals(0, new Polynomial.Rational(0l).getDegree()); + + } + + public void testString() { + + Polynomial.Rational p = new Polynomial.Rational(1l, 3l, -5l); + checkPolynomial(p, "-5 + 3 x + x^2"); + p.setUnknownName("alpha"); + checkPolynomial(p, "-5 + 3 alpha + alpha^2"); + p.setUnknownName(null); + checkPolynomial(p, "-5 + 3 x + x^2"); + + checkPolynomial(new Polynomial.Rational(3l, -2l, 0l), "-2 x + 3 x^2"); + checkPolynomial(new Polynomial.Rational(3l, -2l, 1l), "1 - 2 x + 3 x^2"); + checkPolynomial(new Polynomial.Rational(3l, 2l, 0l), "2 x + 3 x^2"); + checkPolynomial(new Polynomial.Rational(3l, 2l, 1l), "1 + 2 x + 3 x^2"); + checkPolynomial(new Polynomial.Rational(3l, 0l, 1l), "1 + 3 x^2"); + checkPolynomial(new Polynomial.Rational(0l), "0"); + + } + + public void testAddition() { + + Polynomial.Rational p1 = new Polynomial.Rational(1l, -2l); + Polynomial.Rational p2 = new Polynomial.Rational(0l, -1l, 2l); + assertTrue(Polynomial.Rational.add(p1, p2).isZero()); + + p2 = new Polynomial.Rational(p1); + p2.addToSelf(p2); + checkPolynomial(p2, "-4 + 2 x"); + + p1 = new Polynomial.Rational(2l, -4l, 1l); + p2 = new Polynomial.Rational(-2l, 3l, -1l); + p1.addToSelf(p2); + assertEquals(1, p1.getDegree()); + checkPolynomial(p1, "-x"); + + } + + public void testSubtraction() { + + Polynomial.Rational p1 = new Polynomial.Rational(1l, -2l); + assertTrue(Polynomial.Rational.subtract(p1, p1).isZero()); + + Polynomial.Rational p2 = new Polynomial.Rational(6l, -2l); + p2.subtractFromSelf(p1); + checkPolynomial(p2, "5 x"); + + p1 = new Polynomial.Rational(2l, -4l, 1l); + p2 = new Polynomial.Rational(2l, 3l, -1l); + p1.subtractFromSelf(p2); + assertEquals(1, p1.getDegree()); + checkPolynomial(p1, "2 - 7 x"); + + } + + public void testMultiplication() { + + Polynomial.Rational p1 = new Polynomial.Rational(2l, -3l); + Polynomial.Rational p2 = new Polynomial.Rational(1l, 2l, 3l); + checkPolynomial(Polynomial.Rational.multiply(p1, p2), "-9 + x^2 + 2 x^3"); + + p1 = new Polynomial.Rational(1l, 0l); + p2 = new Polynomial.Rational(p1); + for (int i = 2; i < 10; ++i) { + p2.multiplySelf(p1); + checkPolynomial(p2, "x^" + i); + } + + } + + public void testLCM() { + Polynomial.Rational p = new Polynomial.Rational(new RationalNumber(2l, 5l), + new RationalNumber(-1l, 6l), + new RationalNumber(3l, 4l)); + checkPolynomial(p, "3/4 - 1/6 x + 2/5 x^2"); + BigInteger lcm = p.getDenominatorsLCM(); + assertEquals(BigInteger.valueOf(60l), lcm); + p.multiplySelf(lcm); + checkPolynomial(p, "45 - 10 x + 24 x^2"); + } + + public void testEuclidianDivision() { + Polynomial.Rational p = new Polynomial.Rational(4l, 6l, -3l); + Polynomial.Rational q = new Polynomial.Rational(3l, 2l); + Polynomial.DivisionResult res = Polynomial.Rational.euclidianDivision(p, q); + checkPolynomial(res.quotient, "10/9 + 4/3 x"); + checkPolynomial(res.remainder, "-47/9"); + } + + public void checkPolynomial(Polynomial.Rational p, String reference) { + assertEquals(reference, p.toString()); + } + + public static Test suite() { + return new TestSuite(PolynomialRationalTest.class); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/algebra/RationalNumberTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/algebra/RationalNumberTest.java new file mode 100644 index 000000000..8505d9ac4 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/algebra/RationalNumberTest.java @@ -0,0 +1,214 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.algebra; + +import junit.framework.*; + +public class RationalNumberTest + extends TestCase { + + public RationalNumberTest(String name) { + super(name); + } + + public void testNullDenominator() { + try { + new RationalNumber(1l, 0l); + fail("an exception should have been thrown"); + } catch (ArithmeticException e) { + } catch (Exception e) { + fail("wrong exception caught"); + } + } + + public void testToString() { + checkValue(new RationalNumber(1l, 2l), "1/2"); + checkValue(new RationalNumber(-1l, 2l), "-1/2"); + checkValue(new RationalNumber(1l, -2l), "-1/2"); + checkValue(new RationalNumber(-1l, -2l), "1/2"); + checkValue(new RationalNumber(0l, 500l), "0"); + checkValue(new RationalNumber(-12l), "-12"); + checkValue(new RationalNumber(12l), "12"); + } + + public void testSimplification() { + checkValue(new RationalNumber(2l, 4l), "1/2"); + checkValue(new RationalNumber(307692l, 999999l), "4/13"); + checkValue(new RationalNumber(999999l, 307692l), "13/4"); + } + + public void testInvert() { + + RationalNumber f = new RationalNumber(2l, 4l); + f.invertSelf(); + checkValue(f, "2"); + f.invertSelf(); + checkValue(f, "1/2"); + + f = new RationalNumber(120l); + f.invertSelf(); + checkValue(f, "1/120"); + + f = new RationalNumber(0l, 4l); + try { + f.invertSelf(); + fail("an exception should have been thrown"); + } catch (ArithmeticException e) { + } catch (Exception e) { + fail("wrong exception caught"); + } + + f = new RationalNumber(307692l, 999999l); + RationalNumber fInverse = RationalNumber.invert(f); + checkValue(fInverse, "13/4"); + checkValue(f, "4/13"); + + } + + public void testAddition() { + + RationalNumber f1 = new RationalNumber(4l, 6l); + f1.addToSelf(f1); + checkValue(f1, "4/3"); + + checkValue(RationalNumber.add(new RationalNumber(17l, 3l), + new RationalNumber(-17l, 3l)), + "0"); + checkValue(RationalNumber.add(new RationalNumber(2l, 3l), + new RationalNumber(3l, 4l)), + "17/12"); + checkValue(RationalNumber.add(new RationalNumber(1l, 6l), + new RationalNumber(2l, 6l)), + "1/2"); + checkValue(RationalNumber.add(new RationalNumber(4l, 5l), + new RationalNumber(-3l, 4l)), + "1/20"); + checkValue(RationalNumber.add(new RationalNumber(-3l, 4l), + new RationalNumber(4l, 5l)), + "1/20"); + + } + + public void testSubtraction() { + + RationalNumber f1 = new RationalNumber(4l, 6l); + f1.subtractFromSelf(f1); + checkValue(f1, "0"); + + checkValue(RationalNumber.subtract(new RationalNumber(7l, 3l), + new RationalNumber(-7l, 3l)), + "14/3"); + + checkValue(RationalNumber.subtract(new RationalNumber(3l, 4l), + new RationalNumber(2l, 3l)), + "1/12"); + checkValue(RationalNumber.subtract(new RationalNumber(3l, 4l), + new RationalNumber(-2l, 3l)), + "17/12"); + checkValue(RationalNumber.subtract(new RationalNumber(-3l, 4l), + new RationalNumber(2l, 3l)), + "-17/12"); + checkValue(RationalNumber.subtract(new RationalNumber(-3l, 4l), + new RationalNumber(-2l, 3l)), + "-1/12"); + + checkValue(RationalNumber.subtract(new RationalNumber(2l, 3l), + new RationalNumber(3l, 4l)), + "-1/12"); + checkValue(RationalNumber.subtract(new RationalNumber(-2l, 3l), + new RationalNumber(3l, 4l)), + "-17/12"); + checkValue(RationalNumber.subtract(new RationalNumber(2l, 3l), + new RationalNumber(-3l, 4l)), + "17/12"); + checkValue(RationalNumber.subtract(new RationalNumber(-2l, 3l), + new RationalNumber(-3l, 4l)), + "1/12"); + + checkValue(RationalNumber.subtract(new RationalNumber(1l, 6l), + new RationalNumber(2l, 6l)), + "-1/6"); + checkValue(RationalNumber.subtract(new RationalNumber(1l, 2l), + new RationalNumber(1l, 6l)), + "1/3"); + + } + + public void testMultiplication() { + + RationalNumber f = new RationalNumber(2l, 3l); + f.multiplySelf(new RationalNumber(9l,4l)); + checkValue(f, "3/2"); + + checkValue(RationalNumber.multiply(new RationalNumber(1l, 2l), + new RationalNumber(0l)), + "0"); + checkValue(RationalNumber.multiply(new RationalNumber(4l, 15l), + new RationalNumber(-5l, 2l)), + "-2/3"); + checkValue(RationalNumber.multiply(new RationalNumber(-4l, 15l), + new RationalNumber(5l, 2l)), + "-2/3"); + checkValue(RationalNumber.multiply(new RationalNumber(4l, 15l), + new RationalNumber(5l, 2l)), + "2/3"); + checkValue(RationalNumber.multiply(new RationalNumber(-4l, 15l), + new RationalNumber(-5l, 2l)), + "2/3"); + + } + + public void testDivision() { + + RationalNumber f = new RationalNumber(2l, 3l); + f.divideSelf(new RationalNumber(4l,9l)); + checkValue(f, "3/2"); + + try { + RationalNumber.divide(new RationalNumber(1l, 2l), + new RationalNumber(0l)); + fail("an exception should have been thrown"); + } catch (ArithmeticException e) { + } catch (Exception e) { + fail("wrong exception caught"); + } + + checkValue(RationalNumber.divide(new RationalNumber(4l, 15l), + new RationalNumber(-2l, 5l)), + "-2/3"); + checkValue(RationalNumber.divide(new RationalNumber(-4l, 15l), + new RationalNumber(2l, 5l)), + "-2/3"); + checkValue(RationalNumber.divide(new RationalNumber(4l, 15l), + new RationalNumber(2l, 5l)), + "2/3"); + checkValue(RationalNumber.divide(new RationalNumber(-4l, 15l), + new RationalNumber(-2l, 5l)), + "2/3"); + + } + + private void checkValue(RationalNumber f, String reference) { + assertTrue(f.toString().equals(reference)); + } + + public static Test suite() { + return new TestSuite(RationalNumberTest.class); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/estimation/AllTests.java b/src/mantissa/tests-src/org/spaceroots/mantissa/estimation/AllTests.java new file mode 100644 index 000000000..7dd7940d6 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/estimation/AllTests.java @@ -0,0 +1,38 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.estimation; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class AllTests { + + public static Test suite() { + + TestSuite suite = new TestSuite("org.spaceroots.mantissa.estimation"); + + suite.addTest(EstimatedParameterTest.suite()); + suite.addTest(WeightedMeasurementTest.suite()); + suite.addTest(GaussNewtonEstimatorTest.suite()); + suite.addTest(LevenbergMarquardtEstimatorTest.suite()); + + return suite; + + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/estimation/EstimatedParameterTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/estimation/EstimatedParameterTest.java new file mode 100644 index 000000000..e79e9edba --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/estimation/EstimatedParameterTest.java @@ -0,0 +1,74 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.estimation; + +import junit.framework.*; + +public class EstimatedParameterTest + extends TestCase { + + public EstimatedParameterTest(String name) { + super(name); + } + + public void testConstruction() { + + EstimatedParameter p1 = new EstimatedParameter("p1", 1.0); + assertTrue(p1.getName().equals("p1")); + checkValue(p1.getEstimate(), 1.0); + assertTrue(! p1.isBound()); + + EstimatedParameter p2 = new EstimatedParameter("p2", 2.0, true); + assertTrue(p2.getName().equals("p2")); + checkValue(p2.getEstimate(), 2.0); + assertTrue(p2.isBound()); + + } + + public void testBound() { + + EstimatedParameter p = new EstimatedParameter("p", 0.0); + assertTrue(! p.isBound()); + p.setBound(true); + assertTrue(p.isBound()); + p.setBound(false); + assertTrue(! p.isBound()); + + } + + public void testEstimate() { + + EstimatedParameter p = new EstimatedParameter("p", 0.0); + checkValue(p.getEstimate(), 0.0); + + for (double e = 0.0; e < 10.0; e += 0.5) { + p.setEstimate(e); + checkValue(p.getEstimate(), e); + } + + } + + public static Test suite() { + return new TestSuite(EstimatedParameterTest.class); + } + + private void checkValue(double value, double expected) { + assertTrue(Math.abs(value - expected) < 1.0e-10); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/estimation/GaussNewtonEstimatorTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/estimation/GaussNewtonEstimatorTest.java new file mode 100644 index 000000000..60c240658 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/estimation/GaussNewtonEstimatorTest.java @@ -0,0 +1,262 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.estimation; + +import java.util.Random; +import junit.framework.*; + +public class GaussNewtonEstimatorTest + extends TestCase + implements EstimationProblem { + + public GaussNewtonEstimatorTest(String name) { + super(name); + } + + public void testNoMeasurementError() + throws EstimationException { + initRandomizedGrid(2.3); + initProblem(0.0); + GaussNewtonEstimator estimator = + new GaussNewtonEstimator(100, 1.0e-7, 1.0e-10, 1.0e-10); + estimator.estimate(this); + checkGrid(0.01); + } + + public void testSmallMeasurementError() + throws EstimationException { + initRandomizedGrid(2.3); + initProblem(0.02); + GaussNewtonEstimator estimator = + new GaussNewtonEstimator(100, 1.0e-7, 1.0e-10, 1.0e-10); + estimator.estimate(this); + checkGrid(0.1); + } + + public void testNoError() + throws EstimationException { + initRandomizedGrid(0.0); + initProblem(0.0); + GaussNewtonEstimator estimator = + new GaussNewtonEstimator(100, 1.0e-7, 1.0e-10, 1.0e-10); + estimator.estimate(this); + checkGrid(1.0e-10); + } + + public void testUnsolvableProblem() { + + initRandomizedGrid(2.3); + initProblem(0.0); + + // reduce the number of measurements below the limit threshold + int unknowns = unboundPars.length; + WeightedMeasurement[] reducedSet = new WeightedMeasurement[unknowns - 1]; + for (int i = 0; i < reducedSet.length; ++i) { + reducedSet[i] = measurements[i]; + } + measurements = reducedSet; + + boolean gotIt = false; + try { + GaussNewtonEstimator estimator = + new GaussNewtonEstimator(100, 1.0e-7, 1.0e-10, 1.0e-10); + estimator.estimate(this); + } catch(EstimationException e) { + gotIt = true; + } + + assertTrue(gotIt); + + } + + public static Test suite() { + return new TestSuite(GaussNewtonEstimatorTest.class); + } + + public void setUp() { + initPerfectGrid(5); + } + + public void tearDown() { + perfectPars = null; + randomizedPars = null; + unboundPars = null; + measurements = null; + } + + private void initPerfectGrid(int gridSize) { + perfectPars = new EstimatedParameter[gridSize * gridSize * 2]; + + int k = 0; + for (int i = 0; i < gridSize; ++i) { + for (int j = 0; j < gridSize; ++j) { + + String name = new Integer(k).toString(); + perfectPars[2 * k] = new EstimatedParameter("x" + name, i); + perfectPars[2 * k + 1] = new EstimatedParameter("y" + name, j); + ++k; + } + } + + } + + private void initRandomizedGrid(double initialGuessError) { + Random randomizer = new Random(2353995334l); + randomizedPars = new EstimatedParameter[perfectPars.length]; + + // add an error to every point coordinate + for (int k = 0; k < randomizedPars.length; ++k) { + String name = perfectPars[k].getName(); + double value = perfectPars[k].getEstimate(); + double error = randomizer.nextGaussian() * initialGuessError; + randomizedPars[k] = new EstimatedParameter(name, value + error); + } + + } + + private void initProblem(double measurementError) { + + int pointsNumber = randomizedPars.length / 2; + int measurementsNumber = pointsNumber * (pointsNumber - 1) / 2; + measurements = new WeightedMeasurement[measurementsNumber]; + + Random randomizer = new Random(5785631926l); + + // for the test, we consider that the perfect grid is the reality + // and that the randomized grid is the first (wrong) estimate. + int i = 0; + for (int l = 0; l < (pointsNumber - 1); ++l) { + for (int m = l + 1; m < pointsNumber; ++m) { + // perfect measurements on the real data + double dx = perfectPars[2 * l].getEstimate() + - perfectPars[2 * m].getEstimate(); + double dy = perfectPars[2 * l + 1].getEstimate() + - perfectPars[2 * m + 1].getEstimate(); + double d = Math.sqrt(dx * dx + dy * dy); + + // adding a noise to the measurements + d += randomizer.nextGaussian() * measurementError; + + // add the measurement to the current problem + measurements[i++] = new Distance(1.0, d, + randomizedPars[2 * l], + randomizedPars[2 * l + 1], + randomizedPars[2 * m], + randomizedPars[2 * m + 1]); + + } + } + + // fix three values in the randomized grid and bind them (there + // are two abscissas and one ordinate, so if there were no error + // at all, the estimated grid should be correctly centered on the + // perfect grid) + int oddNumber = 2 * (randomizedPars.length / 4) - 1; + for (int k = 0; k < 2 * oddNumber + 1; k += oddNumber) { + randomizedPars[k].setEstimate(perfectPars[k].getEstimate()); + randomizedPars[k].setBound(true); + } + + // store the unbound parameters in a specific table + unboundPars = new EstimatedParameter[randomizedPars.length - 3]; + for (int src = 0, dst = 0; src < randomizedPars.length; ++src) { + if (! randomizedPars[src].isBound()) { + unboundPars[dst++] = randomizedPars[src]; + } + } + + } + + private void checkGrid(double threshold) { + + double rms = 0; + for (int i = 0; i < perfectPars.length; ++i) { + rms += perfectPars[i].getEstimate() - randomizedPars[i].getEstimate(); + } + rms = Math.sqrt(rms / perfectPars.length); + + assertTrue(rms <= threshold); + + } + + private class Distance extends WeightedMeasurement { + + public Distance(double weight, double measuredValue, + EstimatedParameter x1, EstimatedParameter y1, + EstimatedParameter x2, EstimatedParameter y2) { + super(weight, measuredValue); + this.x1 = x1; + this.y1 = y1; + this.x2 = x2; + this.y2 = y2; + } + + public double getTheoreticalValue() { + double dx = x2.getEstimate() - x1.getEstimate(); + double dy = y2.getEstimate() - y1.getEstimate(); + return Math.sqrt(dx * dx + dy * dy); + } + + public double getPartial(EstimatedParameter p) { + + // first quick answer for most parameters + if ((p != x1) && (p != y1) && (p != x2) && (p != y2)) { + return 0.0; + } + + // compute the value now as we know we depend on the specified parameter + double distance = getTheoreticalValue(); + + if (p == x1) { + return (x1.getEstimate() - x2.getEstimate()) / distance; + } else if (p == x2) { + return (x2.getEstimate() - x1.getEstimate()) / distance; + } else if (p == y1) { + return (y1.getEstimate() - y2.getEstimate()) / distance; + } else { + return (y2.getEstimate() - y1.getEstimate()) / distance; + } + + } + + private EstimatedParameter x1; + private EstimatedParameter y1; + private EstimatedParameter x2; + private EstimatedParameter y2; + private static final long serialVersionUID = 4090004243280980746L; + + } + + public WeightedMeasurement[] getMeasurements() { + return measurements; + } + + public EstimatedParameter[] getUnboundParameters() { + return unboundPars; + } + + public EstimatedParameter[] getAllParameters() { + return randomizedPars; + } + + private EstimatedParameter[] perfectPars; + private EstimatedParameter[] randomizedPars; + private EstimatedParameter[] unboundPars; + private WeightedMeasurement[] measurements; + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/estimation/LevenbergMarquardtEstimatorTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/estimation/LevenbergMarquardtEstimatorTest.java new file mode 100644 index 000000000..f87aa4a08 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/estimation/LevenbergMarquardtEstimatorTest.java @@ -0,0 +1,2099 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.estimation; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.Set; + +import junit.framework.*; + +/** + *

    Some of the unit tests are re-implementations of the MINPACK file17 and file22 test files. + * The redistribution policy for MINPACK is available here, for + * convenience, it is reproduced below.

    + + * + * + * + *
    + * Minpack Copyright Notice (1999) University of Chicago. + * All rights reserved + *
    + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + *
      + *
    1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer.
    2. + *
    3. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution.
    4. + *
    5. The end-user documentation included with the redistribution, if any, + * must include the following acknowledgment: + * This product includes software developed by the University of + * Chicago, as Operator of Argonne National Laboratory. + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear.
    6. + *
    7. WARRANTY DISCLAIMER. THE SOFTWARE IS SUPPLIED "AS IS" + * WITHOUT WARRANTY OF ANY KIND. THE COPYRIGHT HOLDER, THE + * UNITED STATES, THE UNITED STATES DEPARTMENT OF ENERGY, AND + * THEIR EMPLOYEES: (1) DISCLAIM ANY WARRANTIES, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO ANY IMPLIED WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE + * OR NON-INFRINGEMENT, (2) DO NOT ASSUME ANY LEGAL LIABILITY + * OR RESPONSIBILITY FOR THE ACCURACY, COMPLETENESS, OR + * USEFULNESS OF THE SOFTWARE, (3) DO NOT REPRESENT THAT USE OF + * THE SOFTWARE WOULD NOT INFRINGE PRIVATELY OWNED RIGHTS, (4) + * DO NOT WARRANT THAT THE SOFTWARE WILL FUNCTION + * UNINTERRUPTED, THAT IT IS ERROR-FREE OR THAT ANY ERRORS WILL + * BE CORRECTED.
    8. + *
    9. LIMITATION OF LIABILITY. IN NO EVENT WILL THE COPYRIGHT + * HOLDER, THE UNITED STATES, THE UNITED STATES DEPARTMENT OF + * ENERGY, OR THEIR EMPLOYEES: BE LIABLE FOR ANY INDIRECT, + * INCIDENTAL, CONSEQUENTIAL, SPECIAL OR PUNITIVE DAMAGES OF + * ANY KIND OR NATURE, INCLUDING BUT NOT LIMITED TO LOSS OF + * PROFITS OR LOSS OF DATA, FOR ANY REASON WHATSOEVER, WHETHER + * SUCH LIABILITY IS ASSERTED ON THE BASIS OF CONTRACT, TORT + * (INCLUDING NEGLIGENCE OR STRICT LIABILITY), OR OTHERWISE, + * EVEN IF ANY OF SAID PARTIES HAS BEEN WARNED OF THE + * POSSIBILITY OF SUCH LOSS OR DAMAGES.
    10. + *
      + + * @author Argonne National Laboratory. MINPACK project. March 1980 (original fortran minpack tests) + * @author Burton S. Garbow (original fortran minpack tests) + * @author Kenneth E. Hillstrom (original fortran minpack tests) + * @author Jorge J. More (original fortran minpack tests) + * @author Luc Maisonobe (non-minpack tests and minpack tests Java translation) + */ +public class LevenbergMarquardtEstimatorTest + extends TestCase { + + public LevenbergMarquardtEstimatorTest(String name) { + super(name); + } + + public void testTrivial() throws EstimationException { + LinearProblem problem = + new LinearProblem(new LinearMeasurement[] { + new LinearMeasurement(new double[] {2}, + new EstimatedParameter[] { + new EstimatedParameter("p0", 0) + }, 3.0) + }); + LevenbergMarquardtEstimator estimator = new LevenbergMarquardtEstimator(); + estimator.estimate(problem); + assertEquals(0, estimator.getRMS(problem), 1.0e-10); + assertEquals(1.5, + problem.getUnboundParameters()[0].getEstimate(), + 1.0e-10); + } + + public void testQRColumnsPermutation() throws EstimationException { + + EstimatedParameter[] x = { + new EstimatedParameter("p0", 0), new EstimatedParameter("p1", 0) + }; + LinearProblem problem = new LinearProblem(new LinearMeasurement[] { + new LinearMeasurement(new double[] { 1.0, -1.0 }, + new EstimatedParameter[] { x[0], x[1] }, + 4.0), + new LinearMeasurement(new double[] { 2.0 }, + new EstimatedParameter[] { x[1] }, + 6.0), + new LinearMeasurement(new double[] { 1.0, -2.0 }, + new EstimatedParameter[] { x[0], x[1] }, + 1.0) + }); + + LevenbergMarquardtEstimator estimator = new LevenbergMarquardtEstimator(); + estimator.estimate(problem); + assertEquals(0, estimator.getRMS(problem), 1.0e-10); + assertEquals(7.0, x[0].getEstimate(), 1.0e-10); + assertEquals(3.0, x[1].getEstimate(), 1.0e-10); + + } + + public void testNoDependency() throws EstimationException { + EstimatedParameter[] p = new EstimatedParameter[] { + new EstimatedParameter("p0", 0), + new EstimatedParameter("p1", 0), + new EstimatedParameter("p2", 0), + new EstimatedParameter("p3", 0), + new EstimatedParameter("p4", 0), + new EstimatedParameter("p5", 0) + }; + LinearProblem problem = new LinearProblem(new LinearMeasurement[] { + new LinearMeasurement(new double[] {2}, new EstimatedParameter[] { p[0] }, 0.0), + new LinearMeasurement(new double[] {2}, new EstimatedParameter[] { p[1] }, 1.1), + new LinearMeasurement(new double[] {2}, new EstimatedParameter[] { p[2] }, 2.2), + new LinearMeasurement(new double[] {2}, new EstimatedParameter[] { p[3] }, 3.3), + new LinearMeasurement(new double[] {2}, new EstimatedParameter[] { p[4] }, 4.4), + new LinearMeasurement(new double[] {2}, new EstimatedParameter[] { p[5] }, 5.5) + }); + LevenbergMarquardtEstimator estimator = new LevenbergMarquardtEstimator(); + estimator.estimate(problem); + assertEquals(0, estimator.getRMS(problem), 1.0e-10); + for (int i = 0; i < p.length; ++i) { + assertEquals(0.55 * i, p[i].getEstimate(), 1.0e-10); + } +} + + public void testOneSet() throws EstimationException { + + EstimatedParameter[] p = { + new EstimatedParameter("p0", 0), + new EstimatedParameter("p1", 0), + new EstimatedParameter("p2", 0) + }; + LinearProblem problem = new LinearProblem(new LinearMeasurement[] { + new LinearMeasurement(new double[] { 1.0 }, + new EstimatedParameter[] { p[0] }, + 1.0), + new LinearMeasurement(new double[] { -1.0, 1.0 }, + new EstimatedParameter[] { p[0], p[1] }, + 1.0), + new LinearMeasurement(new double[] { -1.0, 1.0 }, + new EstimatedParameter[] { p[1], p[2] }, + 1.0) + }); + + LevenbergMarquardtEstimator estimator = new LevenbergMarquardtEstimator(); + estimator.estimate(problem); + assertEquals(0, estimator.getRMS(problem), 1.0e-10); + assertEquals(1.0, p[0].getEstimate(), 1.0e-10); + assertEquals(2.0, p[1].getEstimate(), 1.0e-10); + assertEquals(3.0, p[2].getEstimate(), 1.0e-10); + + } + + public void testTwoSets() throws EstimationException { + EstimatedParameter[] p = { + new EstimatedParameter("p0", 0), + new EstimatedParameter("p1", 1), + new EstimatedParameter("p2", 2), + new EstimatedParameter("p3", 3), + new EstimatedParameter("p4", 4), + new EstimatedParameter("p5", 5) + }; + + double epsilon = 1.0e-7; + LinearProblem problem = new LinearProblem(new LinearMeasurement[] { + + // 4 elements sub-problem + new LinearMeasurement(new double[] { 2.0, 1.0, 4.0 }, + new EstimatedParameter[] { p[0], p[1], p[3] }, + 2.0), + new LinearMeasurement(new double[] { -4.0, -2.0, 3.0, -7.0 }, + new EstimatedParameter[] { p[0], p[1], p[2], p[3] }, + -9.0), + new LinearMeasurement(new double[] { 4.0, 1.0, -2.0, 8.0 }, + new EstimatedParameter[] { p[0], p[1], p[2], p[3] }, + 2.0), + new LinearMeasurement(new double[] { -3.0, -12.0, -1.0 }, + new EstimatedParameter[] { p[1], p[2], p[3] }, + 2.0), + + // 2 elements sub-problem + new LinearMeasurement(new double[] { epsilon, 1.0 }, + new EstimatedParameter[] { p[4], p[5] }, + 1.0 + epsilon * epsilon), + new LinearMeasurement(new double[] { 1.0, 1.0 }, + new EstimatedParameter[] { p[4], p[5] }, + 2.0) + + }); + + LevenbergMarquardtEstimator estimator = new LevenbergMarquardtEstimator(); + estimator.estimate(problem); + assertEquals(0, estimator.getRMS(problem), 1.0e-10); + assertEquals( 3.0, p[0].getEstimate(), 1.0e-10); + assertEquals( 4.0, p[1].getEstimate(), 1.0e-10); + assertEquals(-1.0, p[2].getEstimate(), 1.0e-10); + assertEquals(-2.0, p[3].getEstimate(), 1.0e-10); + assertEquals( 1.0 + epsilon, p[4].getEstimate(), 1.0e-10); + assertEquals( 1.0 - epsilon, p[5].getEstimate(), 1.0e-10); + + } + + public void testNonInversible() throws EstimationException { + + EstimatedParameter[] p = { + new EstimatedParameter("p0", 0), + new EstimatedParameter("p1", 0), + new EstimatedParameter("p2", 0) + }; + LinearMeasurement[] m = new LinearMeasurement[] { + new LinearMeasurement(new double[] { 1.0, 2.0, -3.0 }, + new EstimatedParameter[] { p[0], p[1], p[2] }, + 1.0), + new LinearMeasurement(new double[] { 2.0, 1.0, 3.0 }, + new EstimatedParameter[] { p[0], p[1], p[2] }, + 1.0), + new LinearMeasurement(new double[] { -3.0, -9.0 }, + new EstimatedParameter[] { p[0], p[2] }, + 1.0) + }; + LinearProblem problem = new LinearProblem(m); + + LevenbergMarquardtEstimator estimator = new LevenbergMarquardtEstimator(); + double initialCost = estimator.getRMS(problem); + estimator.estimate(problem); + assertTrue(estimator.getRMS(problem) < initialCost); + assertTrue(Math.sqrt(m.length) * estimator.getRMS(problem) > 0.6); + double dJ0 = 2 * (m[0].getResidual() * m[0].getPartial(p[0]) + + m[1].getResidual() * m[1].getPartial(p[0]) + + m[2].getResidual() * m[2].getPartial(p[0])); + double dJ1 = 2 * (m[0].getResidual() * m[0].getPartial(p[1]) + + m[1].getResidual() * m[1].getPartial(p[1])); + double dJ2 = 2 * (m[0].getResidual() * m[0].getPartial(p[2]) + + m[1].getResidual() * m[1].getPartial(p[2]) + + m[2].getResidual() * m[2].getPartial(p[2])); + assertEquals(0, dJ0, 1.0e-10); + assertEquals(0, dJ1, 1.0e-10); + assertEquals(0, dJ2, 1.0e-10); + + } + + public void testIllConditioned() throws EstimationException { + EstimatedParameter[] p = { + new EstimatedParameter("p0", 0), + new EstimatedParameter("p1", 1), + new EstimatedParameter("p2", 2), + new EstimatedParameter("p3", 3) + }; + + LinearProblem problem1 = new LinearProblem(new LinearMeasurement[] { + new LinearMeasurement(new double[] { 10.0, 7.0, 8.0, 7.0 }, + new EstimatedParameter[] { p[0], p[1], p[2], p[3] }, + 32.0), + new LinearMeasurement(new double[] { 7.0, 5.0, 6.0, 5.0 }, + new EstimatedParameter[] { p[0], p[1], p[2], p[3] }, + 23.0), + new LinearMeasurement(new double[] { 8.0, 6.0, 10.0, 9.0 }, + new EstimatedParameter[] { p[0], p[1], p[2], p[3] }, + 33.0), + new LinearMeasurement(new double[] { 7.0, 5.0, 9.0, 10.0 }, + new EstimatedParameter[] { p[0], p[1], p[2], p[3] }, + 31.0) + }); + LevenbergMarquardtEstimator estimator1 = new LevenbergMarquardtEstimator(); + estimator1.estimate(problem1); + assertEquals(0, estimator1.getRMS(problem1), 1.0e-10); + assertEquals(1.0, p[0].getEstimate(), 1.0e-10); + assertEquals(1.0, p[1].getEstimate(), 1.0e-10); + assertEquals(1.0, p[2].getEstimate(), 1.0e-10); + assertEquals(1.0, p[3].getEstimate(), 1.0e-10); + + LinearProblem problem2 = new LinearProblem(new LinearMeasurement[] { + new LinearMeasurement(new double[] { 10.0, 7.0, 8.1, 7.2 }, + new EstimatedParameter[] { p[0], p[1], p[2], p[3] }, + 32.0), + new LinearMeasurement(new double[] { 7.08, 5.04, 6.0, 5.0 }, + new EstimatedParameter[] { p[0], p[1], p[2], p[3] }, + 23.0), + new LinearMeasurement(new double[] { 8.0, 5.98, 9.89, 9.0 }, + new EstimatedParameter[] { p[0], p[1], p[2], p[3] }, + 33.0), + new LinearMeasurement(new double[] { 6.99, 4.99, 9.0, 9.98 }, + new EstimatedParameter[] { p[0], p[1], p[2], p[3] }, + 31.0) + }); + LevenbergMarquardtEstimator estimator2 = new LevenbergMarquardtEstimator(); + estimator2.estimate(problem2); + assertEquals(0, estimator2.getRMS(problem2), 1.0e-10); + assertEquals(-81.0, p[0].getEstimate(), 1.0e-8); + assertEquals(137.0, p[1].getEstimate(), 1.0e-8); + assertEquals(-34.0, p[2].getEstimate(), 1.0e-8); + assertEquals( 22.0, p[3].getEstimate(), 1.0e-8); + + } + + public void testMoreEstimatedParametersSimple() throws EstimationException { + + EstimatedParameter[] p = { + new EstimatedParameter("p0", 7), + new EstimatedParameter("p1", 6), + new EstimatedParameter("p2", 5), + new EstimatedParameter("p3", 4) + }; + LinearProblem problem = new LinearProblem(new LinearMeasurement[] { + new LinearMeasurement(new double[] { 3.0, 2.0 }, + new EstimatedParameter[] { p[0], p[1] }, + 7.0), + new LinearMeasurement(new double[] { 1.0, -1.0, 1.0 }, + new EstimatedParameter[] { p[1], p[2], p[3] }, + 3.0), + new LinearMeasurement(new double[] { 2.0, 1.0 }, + new EstimatedParameter[] { p[0], p[2] }, + 5.0) + }); + + LevenbergMarquardtEstimator estimator = new LevenbergMarquardtEstimator(); + estimator.estimate(problem); + assertEquals(0, estimator.getRMS(problem), 1.0e-10); + + } + + public void testMoreEstimatedParametersUnsorted() throws EstimationException { + EstimatedParameter[] p = { + new EstimatedParameter("p0", 2), + new EstimatedParameter("p1", 2), + new EstimatedParameter("p2", 2), + new EstimatedParameter("p3", 2), + new EstimatedParameter("p4", 2), + new EstimatedParameter("p5", 2) + }; + LinearProblem problem = new LinearProblem(new LinearMeasurement[] { + new LinearMeasurement(new double[] { 1.0, 1.0 }, + new EstimatedParameter[] { p[0], p[1] }, + 3.0), + new LinearMeasurement(new double[] { 1.0, 1.0, 1.0 }, + new EstimatedParameter[] { p[2], p[3], p[4] }, + 12.0), + new LinearMeasurement(new double[] { 1.0, -1.0 }, + new EstimatedParameter[] { p[4], p[5] }, + -1.0), + new LinearMeasurement(new double[] { 1.0, -1.0, 1.0 }, + new EstimatedParameter[] { p[3], p[2], p[5] }, + 7.0), + new LinearMeasurement(new double[] { 1.0, -1.0 }, + new EstimatedParameter[] { p[4], p[3] }, + 1.0) + }); + + LevenbergMarquardtEstimator estimator = new LevenbergMarquardtEstimator(); + estimator.estimate(problem); + assertEquals(0, estimator.getRMS(problem), 1.0e-10); + assertEquals(3.0, p[2].getEstimate(), 1.0e-10); + assertEquals(4.0, p[3].getEstimate(), 1.0e-10); + assertEquals(5.0, p[4].getEstimate(), 1.0e-10); + assertEquals(6.0, p[5].getEstimate(), 1.0e-10); + + } + + public void testRedundantEquations() throws EstimationException { + EstimatedParameter[] p = { + new EstimatedParameter("p0", 1), + new EstimatedParameter("p1", 1) + }; + LinearProblem problem = new LinearProblem(new LinearMeasurement[] { + new LinearMeasurement(new double[] { 1.0, 1.0 }, + new EstimatedParameter[] { p[0], p[1] }, + 3.0), + new LinearMeasurement(new double[] { 1.0, -1.0 }, + new EstimatedParameter[] { p[0], p[1] }, + 1.0), + new LinearMeasurement(new double[] { 1.0, 3.0 }, + new EstimatedParameter[] { p[0], p[1] }, + 5.0) + }); + + LevenbergMarquardtEstimator estimator = new LevenbergMarquardtEstimator(); + estimator.estimate(problem); + assertEquals(0, estimator.getRMS(problem), 1.0e-10); + assertEquals(2.0, p[0].getEstimate(), 1.0e-10); + assertEquals(1.0, p[1].getEstimate(), 1.0e-10); + + } + + public void testInconsistentEquations() throws EstimationException { + EstimatedParameter[] p = { + new EstimatedParameter("p0", 1), + new EstimatedParameter("p1", 1) + }; + LinearProblem problem = new LinearProblem(new LinearMeasurement[] { + new LinearMeasurement(new double[] { 1.0, 1.0 }, + new EstimatedParameter[] { p[0], p[1] }, + 3.0), + new LinearMeasurement(new double[] { 1.0, -1.0 }, + new EstimatedParameter[] { p[0], p[1] }, + 1.0), + new LinearMeasurement(new double[] { 1.0, 3.0 }, + new EstimatedParameter[] { p[0], p[1] }, + 4.0) + }); + + LevenbergMarquardtEstimator estimator = new LevenbergMarquardtEstimator(); + estimator.estimate(problem); + assertTrue(estimator.getRMS(problem) > 0.1); + + } + + public void testCircleFitting() throws EstimationException { + Circle circle = new Circle(98.680, 47.345); + circle.addPoint( 30.0, 68.0); + circle.addPoint( 50.0, -6.0); + circle.addPoint(110.0, -20.0); + circle.addPoint( 35.0, 15.0); + circle.addPoint( 45.0, 97.0); + LevenbergMarquardtEstimator estimator = new LevenbergMarquardtEstimator(); + estimator.estimate(circle); + assertTrue(estimator.getCostEvaluations() < 10); + assertTrue(estimator.getJacobianEvaluations() < 10); + double rms = estimator.getRMS(circle); + assertEquals(1.768262623567235, Math.sqrt(circle.getM()) * rms, 1.0e-10); + assertEquals(69.96016176931406, circle.getRadius(), 1.0e-10); + assertEquals(96.07590211815305, circle.getX(), 1.0e-10); + assertEquals(48.13516790438953, circle.getY(), 1.0e-10); + } + + public void testCircleFittingBadInit() throws EstimationException { + Circle circle = new Circle(-12, -12); + double[][] points = new double[][] { + {-0.312967, 0.072366}, {-0.339248, 0.132965}, {-0.379780, 0.202724}, + {-0.390426, 0.260487}, {-0.361212, 0.328325}, {-0.346039, 0.392619}, + {-0.280579, 0.444306}, {-0.216035, 0.470009}, {-0.149127, 0.493832}, + {-0.075133, 0.483271}, {-0.007759, 0.452680}, { 0.060071, 0.410235}, + { 0.103037, 0.341076}, { 0.118438, 0.273884}, { 0.131293, 0.192201}, + { 0.115869, 0.129797}, { 0.072223, 0.058396}, { 0.022884, 0.000718}, + {-0.053355, -0.020405}, {-0.123584, -0.032451}, {-0.216248, -0.032862}, + {-0.278592, -0.005008}, {-0.337655, 0.056658}, {-0.385899, 0.112526}, + {-0.405517, 0.186957}, {-0.415374, 0.262071}, {-0.387482, 0.343398}, + {-0.347322, 0.397943}, {-0.287623, 0.458425}, {-0.223502, 0.475513}, + {-0.135352, 0.478186}, {-0.061221, 0.483371}, { 0.003711, 0.422737}, + { 0.065054, 0.375830}, { 0.108108, 0.297099}, { 0.123882, 0.222850}, + { 0.117729, 0.134382}, { 0.085195, 0.056820}, { 0.029800, -0.019138}, + {-0.027520, -0.072374}, {-0.102268, -0.091555}, {-0.200299, -0.106578}, + {-0.292731, -0.091473}, {-0.356288, -0.051108}, {-0.420561, 0.014926}, + {-0.471036, 0.074716}, {-0.488638, 0.182508}, {-0.485990, 0.254068}, + {-0.463943, 0.338438}, {-0.406453, 0.404704}, {-0.334287, 0.466119}, + {-0.254244, 0.503188}, {-0.161548, 0.495769}, {-0.075733, 0.495560}, + { 0.001375, 0.434937}, { 0.082787, 0.385806}, { 0.115490, 0.323807}, + { 0.141089, 0.223450}, { 0.138693, 0.131703}, { 0.126415, 0.049174}, + { 0.066518, -0.010217}, {-0.005184, -0.070647}, {-0.080985, -0.103635}, + {-0.177377, -0.116887}, {-0.260628, -0.100258}, {-0.335756, -0.056251}, + {-0.405195, -0.000895}, {-0.444937, 0.085456}, {-0.484357, 0.175597}, + {-0.472453, 0.248681}, {-0.438580, 0.347463}, {-0.402304, 0.422428}, + {-0.326777, 0.479438}, {-0.247797, 0.505581}, {-0.152676, 0.519380}, + {-0.071754, 0.516264}, { 0.015942, 0.472802}, { 0.076608, 0.419077}, + { 0.127673, 0.330264}, { 0.159951, 0.262150}, { 0.153530, 0.172681}, + { 0.140653, 0.089229}, { 0.078666, 0.024981}, { 0.023807, -0.037022}, + {-0.048837, -0.077056}, {-0.127729, -0.075338}, {-0.221271, -0.067526} + }; + for (int i = 0; i < points.length; ++i) { + circle.addPoint(points[i][0], points[i][1]); + } + LevenbergMarquardtEstimator estimator = new LevenbergMarquardtEstimator(); + estimator.estimate(circle); + assertTrue(estimator.getCostEvaluations() < 15); + assertTrue(estimator.getJacobianEvaluations() < 10); + assertEquals( 0.030184491196225207, estimator.getRMS(circle), 1.0e-9); + assertEquals( 0.2922350065939634, circle.getRadius(), 1.0e-9); + assertEquals(-0.15173845023862165, circle.getX(), 1.0e-8); + assertEquals( 0.20750021499570379, circle.getY(), 1.0e-8); + } + + public void testMinpackLinearFullRank() + throws EstimationException { + minpackTest(new LinearFullRankFunction(10, 5, 1.0, + 5.0, 2.23606797749979), false); + minpackTest(new LinearFullRankFunction(50, 5, 1.0, + 8.06225774829855, 6.70820393249937), false); + } + + public void testMinpackLinearRank1() + throws EstimationException { + minpackTest(new LinearRank1Function(10, 5, 1.0, + 291.521868819476, 1.4638501094228), false); + minpackTest(new LinearRank1Function(50, 5, 1.0, + 3101.60039334535, 3.48263016573496), false); + } + + public void testMinpackLinearRank1ZeroColsAndRows() + throws EstimationException { + minpackTest(new LinearRank1ZeroColsAndRowsFunction(10, 5, 1.0), false); + minpackTest(new LinearRank1ZeroColsAndRowsFunction(50, 5, 1.0), false); + } + + public void testMinpackRosenbrok() + throws EstimationException { + minpackTest(new RosenbrockFunction(new double[] { -1.2, 1.0 }, + Math.sqrt(24.2)), false); + minpackTest(new RosenbrockFunction(new double[] { -12.0, 10.0 }, + Math.sqrt(1795769.0)), false); + minpackTest(new RosenbrockFunction(new double[] { -120.0, 100.0 }, + 11.0 * Math.sqrt(169000121.0)), false); + } + + public void testMinpackHelicalValley() + throws EstimationException { + minpackTest(new HelicalValleyFunction(new double[] { -1.0, 0.0, 0.0 }, + 50.0), false); + minpackTest(new HelicalValleyFunction(new double[] { -10.0, 0.0, 0.0 }, + 102.95630140987), false); + minpackTest(new HelicalValleyFunction(new double[] { -100.0, 0.0, 0.0}, + 991.261822123701), false); + } + + public void testMinpackPowellSingular() + throws EstimationException { + minpackTest(new PowellSingularFunction(new double[] { 3.0, -1.0, 0.0, 1.0 }, + 14.6628782986152), false); + minpackTest(new PowellSingularFunction(new double[] { 30.0, -10.0, 0.0, 10.0 }, + 1270.9838708654), false); + minpackTest(new PowellSingularFunction(new double[] { 300.0, -100.0, 0.0, 100.0 }, + 126887.903284750), false); + } + + public void testMinpackFreudensteinRoth() + throws EstimationException { + minpackTest(new FreudensteinRothFunction(new double[] { 0.5, -2.0 }, + 20.0124960961895, 6.99887517584575, + new double[] { + 11.4124844654993, + -0.896827913731509 + }), false); + minpackTest(new FreudensteinRothFunction(new double[] { 5.0, -20.0 }, + 12432.833948863, 6.9988751744895, + new double[] { + 11.4130046614746, + -0.896796038685958 + }), false); + minpackTest(new FreudensteinRothFunction(new double[] { 50.0, -200.0 }, + 11426454.595762, 6.99887517242903, + new double[] { + 11.4127817857886, + -0.89680510749204 + }), false); + } + + public void testMinpackBard() + throws EstimationException { + minpackTest(new BardFunction(1.0, 6.45613629515967, 0.0906359603390466, + new double[] { + 0.0824105765758334, + 1.1330366534715, + 2.34369463894115 + }), false); + minpackTest(new BardFunction(10.0, 36.1418531596785, 4.17476870138539, + new double[] { + 0.840666673818329, + -158848033.259565, + -164378671.653535 + }), false); + minpackTest(new BardFunction(100.0, 384.114678637399, 4.17476870135969, + new double[] { + 0.840666673867645, + -158946167.205518, + -164464906.857771 + }), false); + } + + public void testMinpackKowalikOsborne() + throws EstimationException { + minpackTest(new KowalikOsborneFunction(new double[] { 0.25, 0.39, 0.415, 0.39 }, + 0.0728915102882945, + 0.017535837721129, + new double[] { + 0.192807810476249, + 0.191262653354071, + 0.123052801046931, + 0.136053221150517 + }), false); + minpackTest(new KowalikOsborneFunction(new double[] { 2.5, 3.9, 4.15, 3.9 }, + 2.97937007555202, + 0.032052192917937, + new double[] { + 728675.473768287, + -14.0758803129393, + -32977797.7841797, + -20571594.1977912 + }), false); + minpackTest(new KowalikOsborneFunction(new double[] { 25.0, 39.0, 41.5, 39.0 }, + 29.9590617016037, + 0.0175364017658228, + new double[] { + 0.192948328597594, + 0.188053165007911, + 0.122430604321144, + 0.134575665392506 + }), true); + } + + public void testMinpackMeyer() + throws EstimationException { + minpackTest(new MeyerFunction(new double[] { 0.02, 4000.0, 250.0 }, + 41153.4665543031, 9.37794514651874, + new double[] { + 0.00560963647102661, + 6181.34634628659, + 345.223634624144 + }), false); + minpackTest(new MeyerFunction(new double[] { 0.2, 40000.0, 2500.0 }, + 4168216.89130846, 792.917871779501, + new double[] { + 1.42367074157994e-11, + 33695.7133432541, + 901.268527953801 + }), true); + } + + public void testMinpackWatson() + throws EstimationException { + + minpackTest(new WatsonFunction(6, 0.0, + 5.47722557505166, 0.0478295939097601, + new double[] { + -0.0157249615083782, 1.01243488232965, + -0.232991722387673, 1.26043101102818, + -1.51373031394421, 0.99299727291842 + }), false); + minpackTest(new WatsonFunction(6, 10.0, + 6433.12578950026, 0.0478295939096951, + new double[] { + -0.0157251901386677, 1.01243485860105, + -0.232991545843829, 1.26042932089163, + -1.51372776706575, 0.99299573426328 + }), false); + minpackTest(new WatsonFunction(6, 100.0, + 674256.040605213, 0.047829593911544, + new double[] { + -0.0157247019712586, 1.01243490925658, + -0.232991922761641, 1.26043292929555, + -1.51373320452707, 0.99299901922322 + }), false); + + minpackTest(new WatsonFunction(9, 0.0, + 5.47722557505166, 0.00118311459212420, + new double[] { + -0.153070644166722e-4, 0.999789703934597, + 0.0147639634910978, 0.146342330145992, + 1.00082109454817, -2.61773112070507, + 4.10440313943354, -3.14361226236241, + 1.05262640378759 + }), false); + minpackTest(new WatsonFunction(9, 10.0, + 12088.127069307, 0.00118311459212513, + new double[] { + -0.153071334849279e-4, 0.999789703941234, + 0.0147639629786217, 0.146342334818836, + 1.00082107321386, -2.61773107084722, + 4.10440307655564, -3.14361222178686, + 1.05262639322589 + }), false); + minpackTest(new WatsonFunction(9, 100.0, + 1269109.29043834, 0.00118311459212384, + new double[] { + -0.153069523352176e-4, 0.999789703958371, + 0.0147639625185392, 0.146342341096326, + 1.00082104729164, -2.61773101573645, + 4.10440301427286, -3.14361218602503, + 1.05262638516774 + }), false); + + minpackTest(new WatsonFunction(12, 0.0, + 5.47722557505166, 0.217310402535861e-4, + new double[] { + -0.660266001396382e-8, 1.00000164411833, + -0.000563932146980154, 0.347820540050756, + -0.156731500244233, 1.05281515825593, + -3.24727109519451, 7.2884347837505, + -10.271848098614, 9.07411353715783, + -4.54137541918194, 1.01201187975044 + }), false); + minpackTest(new WatsonFunction(12, 10.0, + 19220.7589790951, 0.217310402518509e-4, + new double[] { + -0.663710223017410e-8, 1.00000164411787, + -0.000563932208347327, 0.347820540486998, + -0.156731503955652, 1.05281517654573, + -3.2472711515214, 7.28843489430665, + -10.2718482369638, 9.07411364383733, + -4.54137546533666, 1.01201188830857 + }), false); + minpackTest(new WatsonFunction(12, 100.0, + 2018918.04462367, 0.217310402539845e-4, + new double[] { + -0.663806046485249e-8, 1.00000164411786, + -0.000563932210324959, 0.347820540503588, + -0.156731504091375, 1.05281517718031, + -3.24727115337025, 7.28843489775302, + -10.2718482410813, 9.07411364688464, + -4.54137546660822, 1.0120118885369 + }), false); + + } + + public void testMinpackBox3Dimensional() + throws EstimationException { + minpackTest(new Box3DimensionalFunction(10, new double[] { 0.0, 10.0, 20.0 }, + 32.1115837449572), false); + } + + public void testMinpackJennrichSampson() + throws EstimationException { + minpackTest(new JennrichSampsonFunction(10, new double[] { 0.3, 0.4 }, + 64.5856498144943, 11.1517793413499, + new double[] { + 0.257819926636811, 0.257829976764542 + }), false); + } + + public void testMinpackBrownDennis() + throws EstimationException { + minpackTest(new BrownDennisFunction(20, + new double[] { 25.0, 5.0, -5.0, -1.0 }, + 2815.43839161816, 292.954288244866, + new double[] { + -11.59125141003, 13.2024883984741, + -0.403574643314272, 0.236736269844604 + }), false); + minpackTest(new BrownDennisFunction(20, + new double[] { 250.0, 50.0, -50.0, -10.0 }, + 555073.354173069, 292.954270581415, + new double[] { + -11.5959274272203, 13.2041866926242, + -0.403417362841545, 0.236771143410386 + }), false); + minpackTest(new BrownDennisFunction(20, + new double[] { 2500.0, 500.0, -500.0, -100.0 }, + 61211252.2338581, 292.954306151134, + new double[] { + -11.5902596937374, 13.2020628854665, + -0.403688070279258, 0.236665033746463 + }), false); + } + + public void testMinpackChebyquad() + throws EstimationException { + minpackTest(new ChebyquadFunction(1, 8, 1.0, + 1.88623796907732, 1.88623796907732, + new double[] { 0.5 }), false); + minpackTest(new ChebyquadFunction(1, 8, 10.0, + 5383344372.34005, 1.88424820499951, + new double[] { 0.9817314924684 }), false); + minpackTest(new ChebyquadFunction(1, 8, 100.0, + 0.118088726698392e19, 1.88424820499347, + new double[] { 0.9817314852934 }), false); + minpackTest(new ChebyquadFunction(8, 8, 1.0, + 0.196513862833975, 0.0593032355046727, + new double[] { + 0.0431536648587336, 0.193091637843267, + 0.266328593812698, 0.499999334628884, + 0.500000665371116, 0.733671406187302, + 0.806908362156733, 0.956846335141266 + }), false); + minpackTest(new ChebyquadFunction(9, 9, 1.0, + 0.16994993465202, 0.0, + new double[] { + 0.0442053461357828, 0.199490672309881, + 0.23561910847106, 0.416046907892598, + 0.5, 0.583953092107402, + 0.764380891528940, 0.800509327690119, + 0.955794653864217 + }), false); + minpackTest(new ChebyquadFunction(10, 10, 1.0, + 0.183747831178711, 0.0806471004038253, + new double[] { + 0.0596202671753563, 0.166708783805937, + 0.239171018813509, 0.398885290346268, + 0.398883667870681, 0.601116332129320, + 0.60111470965373, 0.760828981186491, + 0.833291216194063, 0.940379732824644 + }), false); + } + + public void testMinpackBrownAlmostLinear() + throws EstimationException { + minpackTest(new BrownAlmostLinearFunction(10, 0.5, + 16.5302162063499, 0.0, + new double[] { + 0.979430303349862, 0.979430303349862, + 0.979430303349862, 0.979430303349862, + 0.979430303349862, 0.979430303349862, + 0.979430303349862, 0.979430303349862, + 0.979430303349862, 1.20569696650138 + }), false); + minpackTest(new BrownAlmostLinearFunction(10, 5.0, + 9765624.00089211, 0.0, + new double[] { + 0.979430303349865, 0.979430303349865, + 0.979430303349865, 0.979430303349865, + 0.979430303349865, 0.979430303349865, + 0.979430303349865, 0.979430303349865, + 0.979430303349865, 1.20569696650135 + }), false); + minpackTest(new BrownAlmostLinearFunction(10, 50.0, + 0.9765625e17, 0.0, + new double[] { + 1.0, 1.0, 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, 1.0, 1.0 + }), false); + minpackTest(new BrownAlmostLinearFunction(30, 0.5, + 83.476044467848, 0.0, + new double[] { + 0.997754216442807, 0.997754216442807, + 0.997754216442807, 0.997754216442807, + 0.997754216442807, 0.997754216442807, + 0.997754216442807, 0.997754216442807, + 0.997754216442807, 0.997754216442807, + 0.997754216442807, 0.997754216442807, + 0.997754216442807, 0.997754216442807, + 0.997754216442807, 0.997754216442807, + 0.997754216442807, 0.997754216442807, + 0.997754216442807, 0.997754216442807, + 0.997754216442807, 0.997754216442807, + 0.997754216442807, 0.997754216442807, + 0.997754216442807, 0.997754216442807, + 0.997754216442807, 0.997754216442807, + 0.997754216442807, 1.06737350671578 + }), false); + minpackTest(new BrownAlmostLinearFunction(40, 0.5, + 128.026364472323, 0.0, + new double[] { + 1.00000000000002, 1.00000000000002, + 1.00000000000002, 1.00000000000002, + 1.00000000000002, 1.00000000000002, + 1.00000000000002, 1.00000000000002, + 1.00000000000002, 1.00000000000002, + 1.00000000000002, 1.00000000000002, + 1.00000000000002, 1.00000000000002, + 1.00000000000002, 1.00000000000002, + 1.00000000000002, 1.00000000000002, + 1.00000000000002, 1.00000000000002, + 1.00000000000002, 1.00000000000002, + 1.00000000000002, 1.00000000000002, + 1.00000000000002, 1.00000000000002, + 1.00000000000002, 1.00000000000002, + 1.00000000000002, 1.00000000000002, + 1.00000000000002, 1.00000000000002, + 1.00000000000002, 1.00000000000002, + 0.999999999999121 + }), false); + } + + public void testMinpackOsborne1() + throws EstimationException { + minpackTest(new Osborne1Function(new double[] { 0.5, 1.5, -1.0, 0.01, 0.02, }, + 0.937564021037838, 0.00739249260904843, + new double[] { + 0.375410049244025, 1.93584654543108, + -1.46468676748716, 0.0128675339110439, + 0.0221227011813076 + }), false); + } + + public void testMinpackOsborne2() + throws EstimationException { + + minpackTest(new Osborne2Function(new double[] { + 1.3, 0.65, 0.65, 0.7, 0.6, + 3.0, 5.0, 7.0, 2.0, 4.5, 5.5 + }, + 1.44686540984712, 0.20034404483314, + new double[] { + 1.30997663810096, 0.43155248076, + 0.633661261602859, 0.599428560991695, + 0.754179768272449, 0.904300082378518, + 1.36579949521007, 4.82373199748107, + 2.39868475104871, 4.56887554791452, + 5.67534206273052 + }), false); + } + + private void minpackTest(MinpackFunction function, boolean exceptionExpected) { + LevenbergMarquardtEstimator estimator = new LevenbergMarquardtEstimator(); + estimator.setMaxCostEval(100 * (function.getN() + 1)); + estimator.setCostRelativeTolerance(Math.sqrt(2.22044604926e-16)); + estimator.setParRelativeTolerance(Math.sqrt(2.22044604926e-16)); + estimator.setOrthoTolerance(2.22044604926e-16); + assertTrue(function.checkTheoreticalStartCost(estimator.getRMS(function))); + try { + estimator.estimate(function); + assertFalse(exceptionExpected); + } catch (EstimationException lsse) { + assertTrue(exceptionExpected); + } + assertTrue(function.checkTheoreticalMinCost(estimator.getRMS(function))); + assertTrue(function.checkTheoreticalMinParams()); + } + + private static class LinearProblem implements EstimationProblem { + + public LinearProblem(LinearMeasurement[] measurements) { + this.measurements = measurements; + } + + public WeightedMeasurement[] getMeasurements() { + return measurements; + } + + public EstimatedParameter[] getUnboundParameters() { + return getAllParameters(); + } + + public EstimatedParameter[] getAllParameters() { + IdentityHashMap map = new IdentityHashMap(); + for (int i = 0; i < measurements.length; ++i) { + EstimatedParameter[] parameters = measurements[i].getParameters(); + for (int j = 0; j < parameters.length; ++j) { + map.put(parameters[j], null); + } + } + Set set = map.keySet(); + return (EstimatedParameter[]) set.toArray(new EstimatedParameter[set.size()]); + } + + private LinearMeasurement[] measurements; + + } + + private static class LinearMeasurement extends WeightedMeasurement { + + public LinearMeasurement(double[] factors, EstimatedParameter[] parameters, + double setPoint) { + super(1.0, setPoint); + this.factors = factors; + this.parameters = parameters; + } + + public double getTheoreticalValue() { + double v = 0; + for (int i = 0; i < factors.length; ++i) { + v += factors[i] * parameters[i].getEstimate(); + } + return v; + } + + public double getPartial(EstimatedParameter parameter) { + for (int i = 0; i < parameters.length; ++i) { + if (parameters[i] == parameter) { + return factors[i]; + } + } + return 0; + } + + public EstimatedParameter[] getParameters() { + return parameters; + } + + private double[] factors; + private EstimatedParameter[] parameters; + private static final long serialVersionUID = -3922448707008868580L; + + } + + private static class Circle implements EstimationProblem { + + public Circle(double cx, double cy) { + this.cx = new EstimatedParameter("cx", cx); + this.cy = new EstimatedParameter("cy", cy); + points = new ArrayList(); + } + + public void addPoint(double px, double py) { + points.add(new PointModel(px, py)); + } + + public int getM() { + return points.size(); + } + + public WeightedMeasurement[] getMeasurements() { + return (WeightedMeasurement[]) points.toArray(new PointModel[points.size()]); + } + + public EstimatedParameter[] getAllParameters() { + return new EstimatedParameter[] { cx, cy }; + } + + public EstimatedParameter[] getUnboundParameters() { + return new EstimatedParameter[] { cx, cy }; + } + + public double getPartialRadiusX() { + double dRdX = 0; + for (Iterator iterator = points.iterator(); iterator.hasNext();) { + dRdX += ((PointModel) iterator.next()).getPartialDiX(); + } + return dRdX / points.size(); + } + + public double getPartialRadiusY() { + double dRdY = 0; + for (Iterator iterator = points.iterator(); iterator.hasNext();) { + dRdY += ((PointModel) iterator.next()).getPartialDiY(); + } + return dRdY / points.size(); + } + + public double getRadius() { + double r = 0; + for (Iterator iterator = points.iterator(); iterator.hasNext();) { + r += ((PointModel) iterator.next()).getCenterDistance(); + } + return r / points.size(); + } + + public double getX() { + return cx.getEstimate(); + } + + public double getY() { + return cy.getEstimate(); + } + + private class PointModel extends WeightedMeasurement { + + public PointModel(double px, double py) { + super(1.0, 0.0); + this.px = px; + this.py = py; + } + + public double getPartial(EstimatedParameter parameter) { + if (parameter == cx) { + return getPartialDiX() - getPartialRadiusX(); + } else if (parameter == cy) { + return getPartialDiY() - getPartialRadiusY(); + } + return 0; + } + + public double getCenterDistance() { + double dx = px - cx.getEstimate(); + double dy = py - cy.getEstimate(); + return Math.sqrt(dx * dx + dy * dy); + } + + public double getPartialDiX() { + return (cx.getEstimate() - px) / getCenterDistance(); + } + + public double getPartialDiY() { + return (cy.getEstimate() - py) / getCenterDistance(); + } + + public double getTheoreticalValue() { + return getCenterDistance() - getRadius(); + } + + private double px; + private double py; + private static final long serialVersionUID = 1L; + + } + + private EstimatedParameter cx; + private EstimatedParameter cy; + private ArrayList points; + + } + + private static abstract class MinpackFunction implements EstimationProblem { + + protected MinpackFunction(int m, + double[] startParams, + double theoreticalStartCost, + double theoreticalMinCost, + double[] theoreticalMinParams) { + this.m = m; + this.n = startParams.length; + parameters = new EstimatedParameter[n]; + for (int i = 0; i < n; ++i) { + parameters[i] = new EstimatedParameter("p" + i, startParams[i]); + } + this.theoreticalStartCost = theoreticalStartCost; + this.theoreticalMinCost = theoreticalMinCost; + this.theoreticalMinParams = theoreticalMinParams; + this.costAccuracy = 1.0e-8; + this.paramsAccuracy = 1.0e-5; + } + + protected static double[] buildArray(int n, double x) { + double[] array = new double[n]; + Arrays.fill(array, x); + return array; + } + + protected void setCostAccuracy(double costAccuracy) { + this.costAccuracy = costAccuracy; + } + + protected void setParamsAccuracy(double paramsAccuracy) { + this.paramsAccuracy = paramsAccuracy; + } + + public int getN() { + return parameters.length; + } + + public boolean checkTheoreticalStartCost(double rms) { + double threshold = costAccuracy * (1.0 + theoreticalStartCost); + return Math.abs(Math.sqrt(m) * rms - theoreticalStartCost) <= threshold; + } + + public boolean checkTheoreticalMinCost(double rms) { + double threshold = costAccuracy * (1.0 + theoreticalMinCost); + return Math.abs(Math.sqrt(m) * rms - theoreticalMinCost) <= threshold; + } + + public boolean checkTheoreticalMinParams() { + if (theoreticalMinParams != null) { + for (int i = 0; i < theoreticalMinParams.length; ++i) { + double mi = theoreticalMinParams[i]; + double vi = parameters[i].getEstimate(); + if (Math.abs(mi - vi) > (paramsAccuracy * (1.0 + Math.abs(mi)))) { + return false; + } + } + } + return true; + } + + public WeightedMeasurement[] getMeasurements() { + WeightedMeasurement[] measurements = new WeightedMeasurement[m]; + for (int i = 0; i < m; ++i) { + measurements[i] = new MinpackMeasurement(i); + } + return measurements; + } + + public EstimatedParameter[] getUnboundParameters() { + return parameters; + } + + public EstimatedParameter[] getAllParameters() { + return parameters; + } + + protected abstract double[][] getJacobian(); + + protected abstract double[] getResiduals(); + + private class MinpackMeasurement extends WeightedMeasurement { + + public MinpackMeasurement(int index) { + super(1.0, 0.0); + this.index = index; + } + + public double getTheoreticalValue() { + // this is obviously NOT efficient as we recompute the whole vector + // each time we need only one element, but it is only for test + // purposes and is simpler to check. + // This implementation should NOT be taken as an example, it is ugly! + return getResiduals()[index]; + } + + public double getPartial(EstimatedParameter parameter) { + // this is obviously NOT efficient as we recompute the whole jacobian + // each time we need only one element, but it is only for test + // purposes and is simpler to check. + // This implementation should NOT be taken as an example, it is ugly! + for (int j = 0; j < n; ++j) { + if (parameter == parameters[j]) { + return getJacobian()[index][j]; + } + } + return 0; + } + + private int index; + private static final long serialVersionUID = 1L; + + } + + protected int n; + protected int m; + protected EstimatedParameter[] parameters; + protected double theoreticalStartCost; + protected double theoreticalMinCost; + protected double[] theoreticalMinParams; + protected double costAccuracy; + protected double paramsAccuracy; + + } + + private static class LinearFullRankFunction extends MinpackFunction { + + public LinearFullRankFunction(int m, int n, double x0, + double theoreticalStartCost, + double theoreticalMinCost) { + super(m, buildArray(n, x0), theoreticalStartCost, + theoreticalMinCost, buildArray(n, -1.0)); + } + + protected double[][] getJacobian() { + double t = 2.0 / m; + double[][] jacobian = new double[m][]; + for (int i = 0; i < m; ++i) { + jacobian[i] = new double[n]; + for (int j = 0; j < n; ++j) { + jacobian[i][j] = (i == j) ? (1 - t) : -t; + } + } + return jacobian; + } + + protected double[] getResiduals() { + double sum = 0; + for (int i = 0; i < n; ++i) { + sum += parameters[i].getEstimate(); + } + double t = 1 + 2 * sum / m; + double[] f = new double[m]; + for (int i = 0; i < n; ++i) { + f[i] = parameters[i].getEstimate() - t; + } + Arrays.fill(f, n, m, -t); + return f; + } + + } + + private static class LinearRank1Function extends MinpackFunction { + + public LinearRank1Function(int m, int n, double x0, + double theoreticalStartCost, + double theoreticalMinCost) { + super(m, buildArray(n, x0), theoreticalStartCost, theoreticalMinCost, null); + } + + protected double[][] getJacobian() { + double[][] jacobian = new double[m][]; + for (int i = 0; i < m; ++i) { + jacobian[i] = new double[n]; + for (int j = 0; j < n; ++j) { + jacobian[i][j] = (i + 1) * (j + 1); + } + } + return jacobian; + } + + protected double[] getResiduals() { + double[] f = new double[m]; + double sum = 0; + for (int i = 0; i < n; ++i) { + sum += (i + 1) * parameters[i].getEstimate(); + } + for (int i = 0; i < m; ++i) { + f[i] = (i + 1) * sum - 1; + } + return f; + } + + } + + private static class LinearRank1ZeroColsAndRowsFunction extends MinpackFunction { + + public LinearRank1ZeroColsAndRowsFunction(int m, int n, double x0) { + super(m, buildArray(n, x0), + Math.sqrt(m + (n+1)*(n-2)*(m-2)*(m-1) * ((n+1)*(n-2)*(2*m-3) - 12) / 24.0), + Math.sqrt((m * (m + 3) - 6) / (2.0 * (2 * m - 3))), + null); + } + + protected double[][] getJacobian() { + double[][] jacobian = new double[m][]; + for (int i = 0; i < m; ++i) { + jacobian[i] = new double[n]; + jacobian[i][0] = 0; + for (int j = 1; j < (n - 1); ++j) { + if (i == 0) { + jacobian[i][j] = 0; + } else if (i != (m - 1)) { + jacobian[i][j] = i * (j + 1); + } else { + jacobian[i][j] = 0; + } + } + jacobian[i][n - 1] = 0; + } + return jacobian; + } + + protected double[] getResiduals() { + double[] f = new double[m]; + double sum = 0; + for (int i = 1; i < (n - 1); ++i) { + sum += (i + 1) * parameters[i].getEstimate(); + } + for (int i = 0; i < (m - 1); ++i) { + f[i] = i * sum - 1; + } + f[m - 1] = -1; + return f; + } + + } + + private static class RosenbrockFunction extends MinpackFunction { + + public RosenbrockFunction(double[] startParams, double theoreticalStartCost) { + super(2, startParams, theoreticalStartCost, 0.0, buildArray(2, 1.0)); + } + + protected double[][] getJacobian() { + double x1 = parameters[0].getEstimate(); + return new double[][] { { -20 * x1, 10 }, { -1, 0 } }; + } + + protected double[] getResiduals() { + double x1 = parameters[0].getEstimate(); + double x2 = parameters[1].getEstimate(); + return new double[] { 10 * (x2 - x1 * x1), 1 - x1 }; + } + + } + + private static class HelicalValleyFunction extends MinpackFunction { + + public HelicalValleyFunction(double[] startParams, + double theoreticalStartCost) { + super(3, startParams, theoreticalStartCost, 0.0, + new double[] { 1.0, 0.0, 0.0 }); + } + + protected double[][] getJacobian() { + double x1 = parameters[0].getEstimate(); + double x2 = parameters[1].getEstimate(); + double tmpSquare = x1 * x1 + x2 * x2; + double tmp1 = twoPi * tmpSquare; + double tmp2 = Math.sqrt(tmpSquare); + return new double[][] { + { 100 * x2 / tmp1, -100 * x1 / tmp1, 10 }, + { 10 * x1 / tmp2, 10 * x2 / tmp2, 0 }, + { 0, 0, 1 } + }; + } + + protected double[] getResiduals() { + double x1 = parameters[0].getEstimate(); + double x2 = parameters[1].getEstimate(); + double x3 = parameters[2].getEstimate(); + double tmp1; + if (x1 == 0) { + tmp1 = (x2 >= 0) ? 0.25 : -0.25; + } else { + tmp1 = Math.atan(x2 / x1) / twoPi; + if (x1 < 0) { + tmp1 += 0.5; + } + } + double tmp2 = Math.sqrt(x1 * x1 + x2 * x2); + return new double[] { + 10.0 * (x3 - 10 * tmp1), + 10.0 * (tmp2 - 1), + x3 + }; + } + + private static final double twoPi = 2.0 * Math.PI; + + } + + private static class PowellSingularFunction extends MinpackFunction { + + public PowellSingularFunction(double[] startParams, + double theoreticalStartCost) { + super(4, startParams, theoreticalStartCost, 0.0, buildArray(4, 0.0)); + } + + protected double[][] getJacobian() { + double x1 = parameters[0].getEstimate(); + double x2 = parameters[1].getEstimate(); + double x3 = parameters[2].getEstimate(); + double x4 = parameters[3].getEstimate(); + return new double[][] { + { 1, 10, 0, 0 }, + { 0, 0, sqrt5, -sqrt5 }, + { 0, 2 * (x2 - 2 * x3), -4 * (x2 - 2 * x3), 0 }, + { 2 * sqrt10 * (x1 - x4), 0, 0, -2 * sqrt10 * (x1 - x4) } + }; + } + + protected double[] getResiduals() { + double x1 = parameters[0].getEstimate(); + double x2 = parameters[1].getEstimate(); + double x3 = parameters[2].getEstimate(); + double x4 = parameters[3].getEstimate(); + return new double[] { + x1 + 10 * x2, + sqrt5 * (x3 - x4), + (x2 - 2 * x3) * (x2 - 2 * x3), + sqrt10 * (x1 - x4) * (x1 - x4) + }; + } + + private static final double sqrt5 = Math.sqrt( 5.0); + private static final double sqrt10 = Math.sqrt(10.0); + + } + + private static class FreudensteinRothFunction extends MinpackFunction { + + public FreudensteinRothFunction(double[] startParams, + double theoreticalStartCost, + double theoreticalMinCost, + double[] theoreticalMinParams) { + super(2, startParams, theoreticalStartCost, + theoreticalMinCost, theoreticalMinParams); + } + + protected double[][] getJacobian() { + double x2 = parameters[1].getEstimate(); + return new double[][] { + { 1, x2 * (10 - 3 * x2) - 2 }, + { 1, x2 * ( 2 + 3 * x2) - 14, } + }; + } + + protected double[] getResiduals() { + double x1 = parameters[0].getEstimate(); + double x2 = parameters[1].getEstimate(); + return new double[] { + -13.0 + x1 + ((5.0 - x2) * x2 - 2.0) * x2, + -29.0 + x1 + ((1.0 + x2) * x2 - 14.0) * x2 + }; + } + + } + + private static class BardFunction extends MinpackFunction { + + public BardFunction(double x0, + double theoreticalStartCost, + double theoreticalMinCost, + double[] theoreticalMinParams) { + super(15, buildArray(3, x0), theoreticalStartCost, + theoreticalMinCost, theoreticalMinParams); + } + + protected double[][] getJacobian() { + double x2 = parameters[1].getEstimate(); + double x3 = parameters[2].getEstimate(); + double[][] jacobian = new double[m][]; + for (int i = 0; i < m; ++i) { + double tmp1 = i + 1; + double tmp2 = 15 - i; + double tmp3 = (i <= 7) ? tmp1 : tmp2; + double tmp4 = x2 * tmp2 + x3 * tmp3; + tmp4 *= tmp4; + jacobian[i] = new double[] { -1, tmp1 * tmp2 / tmp4, tmp1 * tmp3 / tmp4 }; + } + return jacobian; + } + + protected double[] getResiduals() { + double x1 = parameters[0].getEstimate(); + double x2 = parameters[1].getEstimate(); + double x3 = parameters[2].getEstimate(); + double[] f = new double[m]; + for (int i = 0; i < m; ++i) { + double tmp1 = i + 1; + double tmp2 = 15 - i; + double tmp3 = (i <= 7) ? tmp1 : tmp2; + f[i] = y[i] - (x1 + tmp1 / (x2 * tmp2 + x3 * tmp3)); + } + return f; + } + + private static final double[] y = { + 0.14, 0.18, 0.22, 0.25, 0.29, + 0.32, 0.35, 0.39, 0.37, 0.58, + 0.73, 0.96, 1.34, 2.10, 4.39 + }; + + } + + private static class KowalikOsborneFunction extends MinpackFunction { + + public KowalikOsborneFunction(double[] startParams, + double theoreticalStartCost, + double theoreticalMinCost, + double[] theoreticalMinParams) { + super(11, startParams, theoreticalStartCost, + theoreticalMinCost, theoreticalMinParams); + if (theoreticalStartCost > 20.0) { + setCostAccuracy(2.0e-4); + setParamsAccuracy(5.0e-3); + } + } + + protected double[][] getJacobian() { + double x1 = parameters[0].getEstimate(); + double x2 = parameters[1].getEstimate(); + double x3 = parameters[2].getEstimate(); + double x4 = parameters[3].getEstimate(); + double[][] jacobian = new double[m][]; + for (int i = 0; i < m; ++i) { + double tmp = v[i] * (v[i] + x3) + x4; + double j1 = -v[i] * (v[i] + x2) / tmp; + double j2 = -v[i] * x1 / tmp; + double j3 = j1 * j2; + double j4 = j3 / v[i]; + jacobian[i] = new double[] { j1, j2, j3, j4 }; + } + return jacobian; + } + + protected double[] getResiduals() { + double x1 = parameters[0].getEstimate(); + double x2 = parameters[1].getEstimate(); + double x3 = parameters[2].getEstimate(); + double x4 = parameters[3].getEstimate(); + double[] f = new double[m]; + for (int i = 0; i < m; ++i) { + f[i] = y[i] - x1 * (v[i] * (v[i] + x2)) / (v[i] * (v[i] + x3) + x4); + } + return f; + } + + private static final double[] v = { + 4.0, 2.0, 1.0, 0.5, 0.25, 0.167, 0.125, 0.1, 0.0833, 0.0714, 0.0625 + }; + + private static final double[] y = { + 0.1957, 0.1947, 0.1735, 0.1600, 0.0844, 0.0627, + 0.0456, 0.0342, 0.0323, 0.0235, 0.0246 + }; + + } + + private static class MeyerFunction extends MinpackFunction { + + public MeyerFunction(double[] startParams, + double theoreticalStartCost, + double theoreticalMinCost, + double[] theoreticalMinParams) { + super(16, startParams, theoreticalStartCost, + theoreticalMinCost, theoreticalMinParams); + if (theoreticalStartCost > 1.0e6) { + setCostAccuracy(7.0e-3); + setParamsAccuracy(2.0e-2); + } + } + + protected double[][] getJacobian() { + double x1 = parameters[0].getEstimate(); + double x2 = parameters[1].getEstimate(); + double x3 = parameters[2].getEstimate(); + double[][] jacobian = new double[m][]; + for (int i = 0; i < m; ++i) { + double temp = 5.0 * (i + 1) + 45.0 + x3; + double tmp1 = x2 / temp; + double tmp2 = Math.exp(tmp1); + double tmp3 = x1 * tmp2 / temp; + jacobian[i] = new double[] { tmp2, tmp3, -tmp1 * tmp3 }; + } + return jacobian; + } + + protected double[] getResiduals() { + double x1 = parameters[0].getEstimate(); + double x2 = parameters[1].getEstimate(); + double x3 = parameters[2].getEstimate(); + double[] f = new double[m]; + for (int i = 0; i < m; ++i) { + f[i] = x1 * Math.exp(x2 / (5.0 * (i + 1) + 45.0 + x3)) - y[i]; + } + return f; + } + + private static final double[] y = { + 34780.0, 28610.0, 23650.0, 19630.0, + 16370.0, 13720.0, 11540.0, 9744.0, + 8261.0, 7030.0, 6005.0, 5147.0, + 4427.0, 3820.0, 3307.0, 2872.0 + }; + + } + + private static class WatsonFunction extends MinpackFunction { + + public WatsonFunction(int n, double x0, + double theoreticalStartCost, + double theoreticalMinCost, + double[] theoreticalMinParams) { + super(31, buildArray(n, x0), theoreticalStartCost, + theoreticalMinCost, theoreticalMinParams); + } + + protected double[][] getJacobian() { + + double[][] jacobian = new double[m][]; + + for (int i = 0; i < (m - 2); ++i) { + double div = (i + 1) / 29.0; + double s2 = 0.0; + double dx = 1.0; + for (int j = 0; j < n; ++j) { + s2 += dx * parameters[j].getEstimate(); + dx *= div; + } + double temp= 2 * div * s2; + dx = 1.0 / div; + jacobian[i] = new double[n]; + for (int j = 0; j < n; ++j) { + jacobian[i][j] = dx * (j - temp); + dx *= div; + } + } + + jacobian[m - 2] = new double[n]; + jacobian[m - 2][0] = 1; + + jacobian[m - 1] = new double[n]; + jacobian[m - 1][0]= -2 * parameters[0].getEstimate(); + jacobian[m - 1][1]= 1; + + return jacobian; + + } + + protected double[] getResiduals() { + double[] f = new double[m]; + for (int i = 0; i < (m - 2); ++i) { + double div = (i + 1) / 29.0; + double s1 = 0; + double dx = 1; + for (int j = 1; j < n; ++j) { + s1 += j * dx * parameters[j].getEstimate(); + dx *= div; + } + double s2 =0; + dx =1; + for (int j = 0; j < n; ++j) { + s2 += dx * parameters[j].getEstimate(); + dx *= div; + } + f[i] = s1 - s2 * s2 - 1; + } + + double x1 = parameters[0].getEstimate(); + double x2 = parameters[1].getEstimate(); + f[m - 2] = x1; + f[m - 1] = x2 - x1 * x1 - 1; + + return f; + + } + + } + + private static class Box3DimensionalFunction extends MinpackFunction { + + public Box3DimensionalFunction(int m, double[] startParams, + double theoreticalStartCost) { + super(m, startParams, theoreticalStartCost, + 0.0, new double[] { 1.0, 10.0, 1.0 }); + } + + protected double[][] getJacobian() { + double x1 = parameters[0].getEstimate(); + double x2 = parameters[1].getEstimate(); + double[][] jacobian = new double[m][]; + for (int i = 0; i < m; ++i) { + double tmp = (i + 1) / 10.0; + jacobian[i] = new double[] { + -tmp * Math.exp(-tmp * x1), + tmp * Math.exp(-tmp * x2), + Math.exp(-i - 1) - Math.exp(-tmp) + }; + } + return jacobian; + } + + protected double[] getResiduals() { + double x1 = parameters[0].getEstimate(); + double x2 = parameters[1].getEstimate(); + double x3 = parameters[2].getEstimate(); + double[] f = new double[m]; + for (int i = 0; i < m; ++i) { + double tmp = (i + 1) / 10.0; + f[i] = Math.exp(-tmp * x1) - Math.exp(-tmp * x2) + + (Math.exp(-i - 1) - Math.exp(-tmp)) * x3; + } + return f; + } + + } + + private static class JennrichSampsonFunction extends MinpackFunction { + + public JennrichSampsonFunction(int m, double[] startParams, + double theoreticalStartCost, + double theoreticalMinCost, + double[] theoreticalMinParams) { + super(m, startParams, theoreticalStartCost, + theoreticalMinCost, theoreticalMinParams); + } + + protected double[][] getJacobian() { + double x1 = parameters[0].getEstimate(); + double x2 = parameters[1].getEstimate(); + double[][] jacobian = new double[m][]; + for (int i = 0; i < m; ++i) { + double t = i + 1; + jacobian[i] = new double[] { -t * Math.exp(t * x1), -t * Math.exp(t * x2) }; + } + return jacobian; + } + + protected double[] getResiduals() { + double x1 = parameters[0].getEstimate(); + double x2 = parameters[1].getEstimate(); + double[] f = new double[m]; + for (int i = 0; i < m; ++i) { + double temp = i + 1; + f[i] = 2 + 2 * temp - Math.exp(temp * x1) - Math.exp(temp * x2); + } + return f; + } + + } + + private static class BrownDennisFunction extends MinpackFunction { + + public BrownDennisFunction(int m, double[] startParams, + double theoreticalStartCost, + double theoreticalMinCost, + double[] theoreticalMinParams) { + super(m, startParams, theoreticalStartCost, + theoreticalMinCost, theoreticalMinParams); + } + + protected double[][] getJacobian() { + double x1 = parameters[0].getEstimate(); + double x2 = parameters[1].getEstimate(); + double x3 = parameters[2].getEstimate(); + double x4 = parameters[3].getEstimate(); + double[][] jacobian = new double[m][]; + for (int i = 0; i < m; ++i) { + double temp = (i + 1) / 5.0; + double ti = Math.sin(temp); + double tmp1 = x1 + temp * x2 - Math.exp(temp); + double tmp2 = x3 + ti * x4 - Math.cos(temp); + jacobian[i] = new double[] { + 2 * tmp1, 2 * temp * tmp1, 2 * tmp2, 2 * ti * tmp2 + }; + } + return jacobian; + } + + protected double[] getResiduals() { + double x1 = parameters[0].getEstimate(); + double x2 = parameters[1].getEstimate(); + double x3 = parameters[2].getEstimate(); + double x4 = parameters[3].getEstimate(); + double[] f = new double[m]; + for (int i = 0; i < m; ++i) { + double temp = (i + 1) / 5.0; + double tmp1 = x1 + temp * x2 - Math.exp(temp); + double tmp2 = x3 + Math.sin(temp) * x4 - Math.cos(temp); + f[i] = tmp1 * tmp1 + tmp2 * tmp2; + } + return f; + } + + } + + private static class ChebyquadFunction extends MinpackFunction { + + private static double[] buildChebyquadArray(int n, double factor) { + double[] array = new double[n]; + double inv = factor / (n + 1); + for (int i = 0; i < n; ++i) { + array[i] = (i + 1) * inv; + } + return array; + } + + public ChebyquadFunction(int n, int m, double factor, + double theoreticalStartCost, + double theoreticalMinCost, + double[] theoreticalMinParams) { + super(m, buildChebyquadArray(n, factor), theoreticalStartCost, + theoreticalMinCost, theoreticalMinParams); + } + + protected double[][] getJacobian() { + + double[][] jacobian = new double[m][]; + for (int i = 0; i < m; ++i) { + jacobian[i] = new double[n]; + } + + double dx = 1.0 / n; + for (int j = 0; j < n; ++j) { + double tmp1 = 1; + double tmp2 = 2 * parameters[j].getEstimate() - 1; + double temp = 2 * tmp2; + double tmp3 = 0; + double tmp4 = 2; + for (int i = 0; i < m; ++i) { + jacobian[i][j] = dx * tmp4; + double ti = 4 * tmp2 + temp * tmp4 - tmp3; + tmp3 = tmp4; + tmp4 = ti; + ti = temp * tmp2 - tmp1; + tmp1 = tmp2; + tmp2 = ti; + } + } + + return jacobian; + + } + + protected double[] getResiduals() { + + double[] f = new double[m]; + + for (int j = 0; j < n; ++j) { + double tmp1 = 1; + double tmp2 = 2 * parameters[j].getEstimate() - 1; + double temp = 2 * tmp2; + for (int i = 0; i < m; ++i) { + f[i] += tmp2; + double ti = temp * tmp2 - tmp1; + tmp1 = tmp2; + tmp2 = ti; + } + } + + double dx = 1.0 / n; + boolean iev = false; + for (int i = 0; i < m; ++i) { + f[i] *= dx; + if (iev) { + f[i] += 1.0 / (i * (i + 2)); + } + iev = ! iev; + } + + return f; + + } + + } + + private static class BrownAlmostLinearFunction extends MinpackFunction { + + public BrownAlmostLinearFunction(int m, double factor, + double theoreticalStartCost, + double theoreticalMinCost, + double[] theoreticalMinParams) { + super(m, buildArray(m, factor), theoreticalStartCost, + theoreticalMinCost, theoreticalMinParams); + } + + protected double[][] getJacobian() { + double[][] jacobian = new double[m][]; + for (int i = 0; i < m; ++i) { + jacobian[i] = new double[n]; + } + + double prod = 1; + for (int j = 0; j < n; ++j) { + prod *= parameters[j].getEstimate(); + for (int i = 0; i < n; ++i) { + jacobian[i][j] = 1; + } + jacobian[j][j] = 2; + } + + for (int j = 0; j < n; ++j) { + EstimatedParameter vj = parameters[j]; + double temp = vj.getEstimate(); + if (temp == 0) { + temp = 1; + prod = 1; + for (int k = 0; k < n; ++k) { + if (k != j) { + prod *= parameters[k].getEstimate(); + } + } + } + jacobian[n - 1][j] = prod / temp; + } + + return jacobian; + + } + + protected double[] getResiduals() { + double[] f = new double[m]; + double sum = -(n + 1); + double prod = 1; + for (int j = 0; j < n; ++j) { + sum += parameters[j].getEstimate(); + prod *= parameters[j].getEstimate(); + } + for (int i = 0; i < n; ++i) { + f[i] = parameters[i].getEstimate() + sum; + } + f[n - 1] = prod - 1; + return f; + } + + } + + private static class Osborne1Function extends MinpackFunction { + + public Osborne1Function(double[] startParams, + double theoreticalStartCost, + double theoreticalMinCost, + double[] theoreticalMinParams) { + super(33, startParams, theoreticalStartCost, + theoreticalMinCost, theoreticalMinParams); + } + + protected double[][] getJacobian() { + double x2 = parameters[1].getEstimate(); + double x3 = parameters[2].getEstimate(); + double x4 = parameters[3].getEstimate(); + double x5 = parameters[4].getEstimate(); + double[][] jacobian = new double[m][]; + for (int i = 0; i < m; ++i) { + double temp = 10.0 * i; + double tmp1 = Math.exp(-temp * x4); + double tmp2 = Math.exp(-temp * x5); + jacobian[i] = new double[] { + -1, -tmp1, -tmp2, temp * x2 * tmp1, temp * x3 * tmp2 + }; + } + return jacobian; + } + + protected double[] getResiduals() { + double x1 = parameters[0].getEstimate(); + double x2 = parameters[1].getEstimate(); + double x3 = parameters[2].getEstimate(); + double x4 = parameters[3].getEstimate(); + double x5 = parameters[4].getEstimate(); + double[] f = new double[m]; + for (int i = 0; i < m; ++i) { + double temp = 10.0 * i; + double tmp1 = Math.exp(-temp * x4); + double tmp2 = Math.exp(-temp * x5); + f[i] = y[i] - (x1 + x2 * tmp1 + x3 * tmp2); + } + return f; + } + + private static final double[] y = { + 0.844, 0.908, 0.932, 0.936, 0.925, 0.908, 0.881, 0.850, 0.818, 0.784, 0.751, + 0.718, 0.685, 0.658, 0.628, 0.603, 0.580, 0.558, 0.538, 0.522, 0.506, 0.490, + 0.478, 0.467, 0.457, 0.448, 0.438, 0.431, 0.424, 0.420, 0.414, 0.411, 0.406 + }; + + } + + private static class Osborne2Function extends MinpackFunction { + + public Osborne2Function(double[] startParams, + double theoreticalStartCost, + double theoreticalMinCost, + double[] theoreticalMinParams) { + super(65, startParams, theoreticalStartCost, + theoreticalMinCost, theoreticalMinParams); + } + + protected double[][] getJacobian() { + double x01 = parameters[0].getEstimate(); + double x02 = parameters[1].getEstimate(); + double x03 = parameters[2].getEstimate(); + double x04 = parameters[3].getEstimate(); + double x05 = parameters[4].getEstimate(); + double x06 = parameters[5].getEstimate(); + double x07 = parameters[6].getEstimate(); + double x08 = parameters[7].getEstimate(); + double x09 = parameters[8].getEstimate(); + double x10 = parameters[9].getEstimate(); + double x11 = parameters[10].getEstimate(); + double[][] jacobian = new double[m][]; + for (int i = 0; i < m; ++i) { + double temp = i / 10.0; + double tmp1 = Math.exp(-x05 * temp); + double tmp2 = Math.exp(-x06 * (temp - x09) * (temp - x09)); + double tmp3 = Math.exp(-x07 * (temp - x10) * (temp - x10)); + double tmp4 = Math.exp(-x08 * (temp - x11) * (temp - x11)); + jacobian[i] = new double[] { + -tmp1, + -tmp2, + -tmp3, + -tmp4, + temp * x01 * tmp1, + x02 * (temp - x09) * (temp - x09) * tmp2, + x03 * (temp - x10) * (temp - x10) * tmp3, + x04 * (temp - x11) * (temp - x11) * tmp4, + -2 * x02 * x06 * (temp - x09) * tmp2, + -2 * x03 * x07 * (temp - x10) * tmp3, + -2 * x04 * x08 * (temp - x11) * tmp4 + }; + } + return jacobian; + } + + protected double[] getResiduals() { + double x01 = parameters[0].getEstimate(); + double x02 = parameters[1].getEstimate(); + double x03 = parameters[2].getEstimate(); + double x04 = parameters[3].getEstimate(); + double x05 = parameters[4].getEstimate(); + double x06 = parameters[5].getEstimate(); + double x07 = parameters[6].getEstimate(); + double x08 = parameters[7].getEstimate(); + double x09 = parameters[8].getEstimate(); + double x10 = parameters[9].getEstimate(); + double x11 = parameters[10].getEstimate(); + double[] f = new double[m]; + for (int i = 0; i < m; ++i) { + double temp = i / 10.0; + double tmp1 = Math.exp(-x05 * temp); + double tmp2 = Math.exp(-x06 * (temp - x09) * (temp - x09)); + double tmp3 = Math.exp(-x07 * (temp - x10) * (temp - x10)); + double tmp4 = Math.exp(-x08 * (temp - x11) * (temp - x11)); + f[i] = y[i] - (x01 * tmp1 + x02 * tmp2 + x03 * tmp3 + x04 * tmp4); + } + return f; + } + + private static final double[] y = { + 1.366, 1.191, 1.112, 1.013, 0.991, + 0.885, 0.831, 0.847, 0.786, 0.725, + 0.746, 0.679, 0.608, 0.655, 0.616, + 0.606, 0.602, 0.626, 0.651, 0.724, + 0.649, 0.649, 0.694, 0.644, 0.624, + 0.661, 0.612, 0.558, 0.533, 0.495, + 0.500, 0.423, 0.395, 0.375, 0.372, + 0.391, 0.396, 0.405, 0.428, 0.429, + 0.523, 0.562, 0.607, 0.653, 0.672, + 0.708, 0.633, 0.668, 0.645, 0.632, + 0.591, 0.559, 0.597, 0.625, 0.739, + 0.710, 0.729, 0.720, 0.636, 0.581, + 0.428, 0.292, 0.162, 0.098, 0.054 + }; + + } + + public static Test suite() { + return new TestSuite(LevenbergMarquardtEstimatorTest.class); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/estimation/WeightedMeasurementTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/estimation/WeightedMeasurementTest.java new file mode 100644 index 000000000..40d38cb38 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/estimation/WeightedMeasurementTest.java @@ -0,0 +1,117 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.estimation; + +import junit.framework.*; + +public class WeightedMeasurementTest + extends TestCase { + + public WeightedMeasurementTest(String name) { + super(name); + } + + public void testConstruction() { + WeightedMeasurement m = new MyMeasurement(3.0, theoretical() + 0.1); + checkValue(m.getWeight(), 3.0); + checkValue(m.getMeasuredValue(), theoretical() + 0.1); + } + + public void testIgnored() { + WeightedMeasurement m = new MyMeasurement(3.0, theoretical() + 0.1); + assertTrue(!m.isIgnored()); + m.setIgnored(true); + assertTrue(m.isIgnored()); + m.setIgnored(false); + assertTrue(!m.isIgnored()); + } + + public void testTheory() { + WeightedMeasurement m = new MyMeasurement(3.0, theoretical() + 0.1); + checkValue(m.getTheoreticalValue(), theoretical()); + checkValue(m.getResidual(), 0.1); + + double oldP1 = p1.getEstimate(); + p1.setEstimate(oldP1 + m.getResidual() / m.getPartial(p1)); + checkValue(m.getResidual(), 0.0); + p1.setEstimate(oldP1); + checkValue(m.getResidual(), 0.1); + + double oldP2 = p2.getEstimate(); + p2.setEstimate(oldP2 + m.getResidual() / m.getPartial(p2)); + checkValue(m.getResidual(), 0.0); + p2.setEstimate(oldP2); + checkValue(m.getResidual(), 0.1); + + } + + public static Test suite() { + return new TestSuite(WeightedMeasurementTest.class); + } + + public void setUp() { + p1 = new EstimatedParameter("p1", 1.0); + p2 = new EstimatedParameter("p2", 2.0); + } + + public void tearDown() { + p1 = null; + p2 = null; + } + + private void checkValue(double value, double expected) { + assertTrue(Math.abs(value - expected) < 1.0e-10); + } + + private double theoretical() { + return 3 * p1.getEstimate() - p2.getEstimate(); + } + + private double partial(EstimatedParameter p) { + if (p == p1) { + return 3.0; + } else if (p == p2) { + return -1.0; + } else { + return 0.0; + } + } + + private class MyMeasurement + extends WeightedMeasurement { + + public MyMeasurement(double weight, double measuredValue) { + super(weight, measuredValue); + } + + public double getTheoreticalValue() { + return theoretical(); + } + + public double getPartial(EstimatedParameter p) { + return partial(p); + } + + private static final long serialVersionUID = -246712922500792332L; + + } + + private EstimatedParameter p1; + private EstimatedParameter p2; + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/fitting/AbstractCurveFitterTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/fitting/AbstractCurveFitterTest.java new file mode 100644 index 000000000..7ea6b32f1 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/fitting/AbstractCurveFitterTest.java @@ -0,0 +1,106 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.fitting; + +import java.util.Random; +import junit.framework.*; + +import org.spaceroots.mantissa.estimation.EstimatedParameter; +import org.spaceroots.mantissa.estimation.WeightedMeasurement; + +public class AbstractCurveFitterTest + extends TestCase { + + public AbstractCurveFitterTest(String name) { + super(name); + } + + public void testAlreadySorted() { + for (double x = 0.0; x < 100.0; x += 1.0) { + fitter.addWeightedPair(1.0, x, 0.0); + } + checkSorted(); + } + + public void testReversed() { + for (double x = 0.0; x < 100.0; x += 1.0) { + fitter.addWeightedPair(1.0, 100.0 - x, 0.0); + } + checkSorted(); + } + + public void testRandom() { + Random randomizer = new Random(86757343594l); + for (int i = 0; i < 100; ++i) { + fitter.addWeightedPair(1.0, 10.0 * randomizer.nextDouble(), 0.0); + } + checkSorted(); + } + + public void checkSorted() { + fitter.doSort(); + + WeightedMeasurement[] measurements = fitter.getMeasurements(); + for (int i = 1; i < measurements.length; ++i) { + AbstractCurveFitter.FitMeasurement m1 + = (AbstractCurveFitter.FitMeasurement) measurements[i-1]; + AbstractCurveFitter.FitMeasurement m2 + = (AbstractCurveFitter.FitMeasurement) measurements[i]; + assertTrue(m1.x <= m2.x); + } + + } + + public static Test suite() { + return new TestSuite(AbstractCurveFitterTest.class); + } + + public void setUp() { + fitter = new DummyFitter(); + } + + public void tearOff() { + fitter = null; + } + + private class DummyFitter + extends AbstractCurveFitter { + + public DummyFitter() { + super(10, 10, 0.0, 0.0, 0.0); + } + + public double valueAt(double x) { + return 0.0; + } + + public double partial(double x, EstimatedParameter p) { + return 0.0; + } + + public void doSort() { + sortMeasurements(); + } + + private static final long serialVersionUID = -5453139487565082528L; + + } + + private DummyFitter fitter; + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/fitting/AllTests.java b/src/mantissa/tests-src/org/spaceroots/mantissa/fitting/AllTests.java new file mode 100644 index 000000000..14d576547 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/fitting/AllTests.java @@ -0,0 +1,35 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.fitting; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class AllTests { + public static Test suite() { + + TestSuite suite= new TestSuite("org.spaceroots.mantissa.fitting"); + + suite.addTest(AbstractCurveFitterTest.suite()); + suite.addTest(PolynomialFitterTest.suite()); + suite.addTest(HarmonicFitterTest.suite()); + + return suite; + + } +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/fitting/HarmonicFitterTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/fitting/HarmonicFitterTest.java new file mode 100644 index 000000000..6c6394f47 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/fitting/HarmonicFitterTest.java @@ -0,0 +1,174 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.fitting; + +import java.util.Random; +import junit.framework.*; + +import org.spaceroots.mantissa.estimation.EstimationException; +import org.spaceroots.mantissa.estimation.WeightedMeasurement; + +public class HarmonicFitterTest + extends TestCase { + + public HarmonicFitterTest(String name) { + super(name); + } + + public void testNoError() + throws EstimationException { + HarmonicFunction f = new HarmonicFunction(0.2, 3.4, 4.1); + + HarmonicFitter fitter = new HarmonicFitter(20, 1.0e-7, + 1.0e-10, 1.0e-10); + for (double x = 0.0; x < 1.3; x += 0.01) { + fitter.addWeightedPair(1.0, x, f.valueAt(x)); + } + + double[] coeffs = fitter.fit(); + + HarmonicFunction fitted = new HarmonicFunction(coeffs[0], + coeffs[1], + coeffs[2]); + assertTrue(Math.abs(coeffs[0] - f.getA()) < 1.0e-12); + assertTrue(Math.abs(coeffs[1] - f.getOmega()) < 1.0e-12); + assertTrue(Math.abs(coeffs[2] - center(f.getPhi(), coeffs[2])) < 1.0e-12); + + for (double x = -1.0; x < 1.0; x += 0.01) { + assertTrue(Math.abs(f.valueAt(x) - fitted.valueAt(x)) < 1.0e-12); + } + + } + + public void test1PercentError() + throws EstimationException { + Random randomizer = new Random(64925784252l); + HarmonicFunction f = new HarmonicFunction(0.2, 3.4, 4.1); + + HarmonicFitter fitter = new HarmonicFitter(20, 1.0e-7, + 1.0e-10, 1.0e-10); + for (double x = 0.0; x < 10.0; x += 0.1) { + fitter.addWeightedPair(1.0, x, + f.valueAt(x) + 0.01 * randomizer.nextGaussian()); + } + + double[] coeffs = fitter.fit(); + + new HarmonicFunction(coeffs[0], coeffs[1], coeffs[2]); + assertTrue(Math.abs(coeffs[0] - f.getA()) < 1.0e-3); + assertTrue(Math.abs(coeffs[1] - f.getOmega()) < 3.5e-3); + assertTrue(Math.abs(coeffs[2] - center(f.getPhi(), coeffs[2])) < 2.0e-2); + + WeightedMeasurement[] measurements = fitter.getMeasurements(); + for (int i = 0; i < measurements.length; ++i) { + WeightedMeasurement m = measurements[i]; + assertTrue(Math.abs(measurements[i].getMeasuredValue() + - m.getTheoreticalValue()) < 0.04); + } + + } + + public void testUnsorted() + throws EstimationException { + Random randomizer = new Random(64925784252l); + HarmonicFunction f = new HarmonicFunction(0.2, 3.4, 4.1); + + HarmonicFitter fitter = new HarmonicFitter(100, 1.0e-7, + 1.0e-10, 1.0e-10); + + // build a regularly spaced array of measurements + int size = 100; + double[] xTab = new double[size]; + double[] yTab = new double[size]; + for (int i = 0; i < size; ++i) { + xTab[i] = 0.1 * i; + yTab[i] = f.valueAt (xTab[i]) + 0.01 * randomizer.nextGaussian(); + } + + // shake it + for (int i = 0; i < size; ++i) { + int i1 = randomizer.nextInt(size); + int i2 = randomizer.nextInt(size); + double xTmp = xTab[i1]; + double yTmp = yTab[i1]; + xTab[i1] = xTab[i2]; + yTab[i1] = yTab[i2]; + xTab[i2] = xTmp; + yTab[i2] = yTmp; + } + + // pass it to the fitter + for (int i = 0; i < size; ++i) { + fitter.addWeightedPair(1.0, xTab[i], yTab[i]); + } + + double[] coeffs = fitter.fit(); + + new HarmonicFunction(coeffs[0], coeffs[1], coeffs[2]); + assertTrue(Math.abs(coeffs[0] - f.getA()) < 1.0e-3); + assertTrue(Math.abs(coeffs[1] - f.getOmega()) < 3.5e-3); + assertTrue(Math.abs(coeffs[2] - center(f.getPhi(), coeffs[2])) < 2.0e-2); + + WeightedMeasurement[] measurements = fitter.getMeasurements(); + for (int i = 0; i < measurements.length; ++i) { + WeightedMeasurement m = measurements[i]; + assertTrue(Math.abs(m.getMeasuredValue() - m.getTheoreticalValue()) + < 0.04); + } + } + + public static Test suite() { + return new TestSuite(HarmonicFitterTest.class); + } + + /** Center an angle with respect to another one. */ + private static double center(double a, double ref) { + double twoPi = Math.PI + Math.PI; + return a - twoPi * Math.floor((a + Math.PI - ref) / twoPi); + } + + private class HarmonicFunction { + public HarmonicFunction(double a, double omega, double phi) { + this.a = a; + this.omega = omega; + this.phi = phi; + } + + public double valueAt(double x) { + return a * Math.cos(omega * x + phi); + } + + public double getA() { + return a; + } + + public double getOmega() { + return omega; + } + + public double getPhi() { + return phi; + } + + private double a; + private double omega; + private double phi; + + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/fitting/PolynomialFitterTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/fitting/PolynomialFitterTest.java new file mode 100644 index 000000000..fc883bff0 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/fitting/PolynomialFitterTest.java @@ -0,0 +1,154 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.fitting; + +import java.util.Random; +import junit.framework.*; + +import org.spaceroots.mantissa.estimation.EstimationException; + +public class PolynomialFitterTest + extends TestCase { + + public PolynomialFitterTest(String name) { + super(name); + } + + public void testNoError() + throws EstimationException { + Random randomizer = new Random(64925784252l); + for (int degree = 0; degree < 10; ++degree) { + Polynom p = new Polynom(degree); + for (int i = 0; i <= degree; ++i) { + p.initCoeff (i, randomizer.nextGaussian()); + } + + PolynomialFitter fitter = new PolynomialFitter(degree, + 10, 1.0e-7, + 1.0e-10, 1.0e-10); + for (int i = 0; i <= degree; ++i) { + fitter.addWeightedPair(1.0, i, p.valueAt(i)); + } + + Polynom fitted = new Polynom(fitter.fit()); + + for (double x = -1.0; x < 1.0; x += 0.01) { + double error = Math.abs(p.valueAt(x) - fitted.valueAt(x)) + / (1.0 + Math.abs(p.valueAt(x))); + assertTrue(Math.abs(error) < 1.0e-5); + } + + } + + } + + public void testSmallError() + throws EstimationException { + Random randomizer = new Random(53882150042l); + for (int degree = 0; degree < 10; ++degree) { + Polynom p = new Polynom(degree); + for (int i = 0; i <= degree; ++i) { + p.initCoeff(i, randomizer.nextGaussian()); + } + + PolynomialFitter fitter = new PolynomialFitter(degree, + 10, 1.0e-7, + 1.0e-10, 1.0e-10); + for (double x = -1.0; x < 1.0; x += 0.01) { + fitter.addWeightedPair(1.0, x, + p.valueAt(x) + 0.1 * randomizer.nextGaussian()); + } + + Polynom fitted = new Polynom(fitter.fit()); + + for (double x = -1.0; x < 1.0; x += 0.01) { + double error = Math.abs(p.valueAt(x) - fitted.valueAt(x)) + / (1.0 + Math.abs(p.valueAt(x))); + assertTrue(Math.abs(error) < 0.1); + } + } + + } + + public void testUnsolvableProblem() + throws EstimationException { + Random randomizer = new Random(1248788532l); + for (int degree = 0; degree < 10; ++degree) { + Polynom p = new Polynom(degree); + for (int i = 1; i <= degree; ++i) { + p.initCoeff(i, randomizer.nextGaussian()); + } + + PolynomialFitter fitter = new PolynomialFitter(degree, + 10, 1.0e-7, + 1.0e-10, 1.0e-10); + + // reusing the same point over and over again does not bring + // information, the problem cannot be solved in this case for + // degrees greater than 1 (but one point is sufficient for + // degree 0) + for (double x = -1.0; x < 1.0; x += 0.01) { + fitter.addWeightedPair(1.0, 0.0, p.valueAt(0.0)); + } + + boolean gotIt = false; + try { + fitter.fit(); + } catch(EstimationException e) { + gotIt = true; + } + assertTrue((degree == 0 && ! gotIt) || (degree > 0 && gotIt)); + + } + + } + + public static Test suite() { + return new TestSuite(PolynomialFitterTest.class); + } + + private class Polynom { + + public Polynom(int degree) { + coeffs = new double[degree + 1]; + for (int i = 0; i < coeffs.length; ++i) { + coeffs[i] = 0.0; + } + } + + public Polynom(double[]coeffs) { + this.coeffs = coeffs; + } + + public void initCoeff(int i, double c) { + coeffs[i] = c; + } + + public double valueAt(double x) { + double y = coeffs[coeffs.length - 1]; + for (int i = coeffs.length - 2; i >= 0; --i) { + y = y * x + coeffs[i]; + } + return y; + } + + private double[] coeffs; + + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/functions/AllTests.java b/src/mantissa/tests-src/org/spaceroots/mantissa/functions/AllTests.java new file mode 100644 index 000000000..07245d7a7 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/functions/AllTests.java @@ -0,0 +1,34 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.functions; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class AllTests { + public static Test suite() { + + TestSuite suite = new TestSuite("org.spaceroots.mantissa.functions"); + + suite.addTest(org.spaceroots.mantissa.functions.scalar.AllTests.suite()); + suite.addTest(org.spaceroots.mantissa.functions.vectorial.AllTests.suite()); + + return suite; + + } +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/functions/scalar/AllTests.java b/src/mantissa/tests-src/org/spaceroots/mantissa/functions/scalar/AllTests.java new file mode 100644 index 000000000..79a5e887b --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/functions/scalar/AllTests.java @@ -0,0 +1,35 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.functions.scalar; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class AllTests { + public static Test suite() { + + TestSuite suite = new TestSuite("org.spaceroots.mantissa.functions.scalar"); + + suite.addTest(ScalarValuedPairTest.suite()); + suite.addTest(ComputableFunctionSamplerTest.suite()); + suite.addTest(BasicSampledFunctionIteratorTest.suite()); + + return suite; + + } +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/functions/scalar/BasicSampledFunctionIteratorTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/functions/scalar/BasicSampledFunctionIteratorTest.java new file mode 100644 index 000000000..59f479718 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/functions/scalar/BasicSampledFunctionIteratorTest.java @@ -0,0 +1,145 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.functions.scalar; + +import org.spaceroots.mantissa.functions.FunctionException; +import org.spaceroots.mantissa.functions.ExhaustedSampleException; + +import junit.framework.*; + +public class BasicSampledFunctionIteratorTest + extends TestCase { + + public BasicSampledFunctionIteratorTest(String name) { + super(name); + } + + public void testIteration() + throws ExhaustedSampleException, FunctionException { + + BasicSampledFunctionIterator iter = + new BasicSampledFunctionIterator(new Function(0.0, 0.1, 10)); + + for (int i = 0; i < 10; ++i) { + assertTrue(iter.hasNext()); + ScalarValuedPair pair = iter.nextSamplePoint(); + assertTrue(Math.abs(pair.getX() - 0.1 * i) < 1.0e-10); + assertTrue(Math.abs(pair.getY() + 0.1 * i) < 1.0e-10); + } + + } + + public void testExhausted() + throws ExhaustedSampleException, FunctionException { + + BasicSampledFunctionIterator iter = + new BasicSampledFunctionIterator(new Function(0.0, 0.1, 10)); + + for (int i = 0; i < 10; ++i) { + assertTrue(iter.hasNext()); + iter.nextSamplePoint(); + } + + assertTrue(! iter.hasNext()); + + boolean exceptionOccurred = false; + try { + iter.nextSamplePoint(); + } catch(ExhaustedSampleException e) { + exceptionOccurred = true; + } + assertTrue(exceptionOccurred); + + } + + public void testUnderlyingException() + throws ExhaustedSampleException, FunctionException { + + BasicSampledFunctionIterator iter = + new BasicSampledFunctionIterator(new SampledFunction() { + + private boolean fireException = false; + + public int size() { + return 2; + } + + public ScalarValuedPair samplePointAt(int i) + throws FunctionException { + if (fireException) { + throw new FunctionException("boom"); + } + fireException = true; + return new ScalarValuedPair(0.0, 0.0); + } + + }); + + boolean exceptionOccurred = false; + try { + iter.nextSamplePoint(); + } catch(FunctionException e) { + exceptionOccurred = true; + } + assertTrue(! exceptionOccurred); + + exceptionOccurred = false; + try { + iter.nextSamplePoint(); + } catch (FunctionException e) { + exceptionOccurred = true; + } + assertTrue(exceptionOccurred); + + } + + public static Test suite() { + return new TestSuite(BasicSampledFunctionIteratorTest.class); + } + + private class Function + implements SampledFunction { + + private double begin; + private double step; + private int n; + + public Function(double begin, double step, int n) { + this.begin = begin; + this.step = step; + this.n = n; + } + + public int size() { + return n; + } + + public ScalarValuedPair samplePointAt(int i) + throws FunctionException { + + if (i < 0 || i >= n) { + throw new FunctionException("outside of range"); + } + + double x = begin + i * step; + return new ScalarValuedPair(x, -x); + + } + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/functions/scalar/ComputableFunctionSamplerTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/functions/scalar/ComputableFunctionSamplerTest.java new file mode 100644 index 000000000..a8f9424e8 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/functions/scalar/ComputableFunctionSamplerTest.java @@ -0,0 +1,191 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.functions.scalar; + +import org.spaceroots.mantissa.functions.FunctionException; + +import junit.framework.*; + +public class ComputableFunctionSamplerTest + extends TestCase { + + public ComputableFunctionSamplerTest(String name) { + super(name); + } + + public void testBeginStepNumber() + throws FunctionException { + + ComputableFunctionSampler sampler = + new ComputableFunctionSampler(new Function(0.0, 1.0), + 0.0, 0.099, 11); + + assertTrue(sampler.size() == 11); + assertTrue(Math.abs(sampler.samplePointAt(0).getX() - 0.000) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(0).getY() - 0.000) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(5).getX() - 0.495) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(5).getY() + 0.495) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(10).getX() - 0.990) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(10).getY() + 0.990) < 1.0e-10); + + } + + public void testRangeNumber() + throws FunctionException { + + double[] range = new double[2]; + range[0] = 0.0; + range[1] = 1.0; + ComputableFunctionSampler sampler = + new ComputableFunctionSampler(new Function(0.0, 1.0), range, 11); + + assertTrue(sampler.size() == 11); + assertTrue(Math.abs(sampler.samplePointAt(0).getX() - 0.0) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(0).getY() - 0.0) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(5).getX() - 0.5) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(5).getY() + 0.5) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(10).getX() - 1.0) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(10).getY() + 1.0) < 1.0e-10); + + } + + public void testRangeStepNoAdjust() + throws FunctionException { + + double[] range = new double[2]; + range[0] = 0.0; + range[1] = 1.0; + ComputableFunctionSampler sampler = + new ComputableFunctionSampler(new Function(0.0, 1.0), + range, 0.083, false); + + assertTrue(sampler.size() == 12); + assertTrue(Math.abs(sampler.samplePointAt(0).getX() - 0.000) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(0).getY() - 0.000) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(5).getX() - 0.415) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(5).getY() + 0.415) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(11).getX() - 0.913) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(11).getY() + 0.913) < 1.0e-10); + + } + + public void testRangeStepAdjust() + throws FunctionException { + + double[] range = new double[2]; + range[0] = 0.0; + range[1] = 1.0; + ComputableFunctionSampler sampler = + new ComputableFunctionSampler(new Function(0.0, 1.0), + range, 0.083, true); + + assertTrue(sampler.size() == 13); + assertTrue(Math.abs(sampler.samplePointAt(0).getX() - 0.0) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(0).getY() - 0.0) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(6).getX() - 0.5) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(6).getY() + 0.5) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(12).getX() - 1.0) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(12).getY() + 1.0) < 1.0e-10); + + } + + public void testOutOfRange() + throws FunctionException { + + ComputableFunctionSampler sampler = + new ComputableFunctionSampler(new Function(0.0, 1.0), 0.0, 1.0, 10); + + boolean exceptionOccurred = false; + try { + sampler.samplePointAt(-1); + } catch(ArrayIndexOutOfBoundsException e) { + exceptionOccurred = true; + } + assertTrue(exceptionOccurred); + + exceptionOccurred = false; + try { + sampler.samplePointAt(10); + } catch(ArrayIndexOutOfBoundsException e) { + exceptionOccurred = true; + } + assertTrue(exceptionOccurred); + + } + + public void testUnderlyingException() { + + ComputableFunctionSampler sampler = + new ComputableFunctionSampler(new ComputableFunction() { + public double valueAt(double x) + throws FunctionException { + if (x < 0.5) { + return -x; + } + throw new FunctionException("upper half range exception"); + } + }, + 0.0, 0.1, 11); + + boolean exceptionOccurred = false; + try { + sampler.samplePointAt(2); + } catch(FunctionException e) { + exceptionOccurred = true; + } + assertTrue(! exceptionOccurred); + + exceptionOccurred = false; + try { + sampler.samplePointAt(8); + } catch(FunctionException e) { + exceptionOccurred = true; + } + assertTrue(exceptionOccurred); + + } + + public static Test suite() { + return new TestSuite(ComputableFunctionSamplerTest.class); + } + + private class Function + implements ComputableFunction { + + private double min; + private double max; + + public Function(double min, double max) { + this.min = min; + this.max = max; + } + + public double valueAt(double x) + throws FunctionException { + + if (x < min || x > max) { + throw new FunctionException("outside of range"); + } + + return -x; + + } + + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/functions/scalar/ScalarValuedPairTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/functions/scalar/ScalarValuedPairTest.java new file mode 100644 index 000000000..c9c09f17c --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/functions/scalar/ScalarValuedPairTest.java @@ -0,0 +1,51 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.functions.scalar; + +import junit.framework.*; + +public class ScalarValuedPairTest + extends TestCase { + + public ScalarValuedPairTest(String name) { + super(name); + } + + public void testConstructor() { + ScalarValuedPair pair = new ScalarValuedPair(1.2, -8.4); + assertTrue(Math.abs(pair.getX() - 1.2) < 1.0e-10); + assertTrue(Math.abs(pair.getY() + 8.4) < 1.0e-10); + } + + public void testCopyConstructor() { + + ScalarValuedPair pair1 = new ScalarValuedPair(1.2, -8.4); + ScalarValuedPair pair2 = new ScalarValuedPair(pair1); + + assertTrue(Math.abs(pair2.getX() - pair1.getX()) < 1.0e-10); + assertTrue(Math.abs(pair2.getY() - pair1.getY()) < 1.0e-10); + assertTrue(Math.abs(pair2.getX() - 1.2) < 1.0e-10); + assertTrue(Math.abs(pair2.getY() + 8.4) < 1.0e-10); + + } + + public static Test suite() { + return new TestSuite(ScalarValuedPairTest.class); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/functions/vectorial/AllTests.java b/src/mantissa/tests-src/org/spaceroots/mantissa/functions/vectorial/AllTests.java new file mode 100644 index 000000000..e14e263dd --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/functions/vectorial/AllTests.java @@ -0,0 +1,35 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.functions.vectorial; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class AllTests { + public static Test suite() { + + TestSuite suite = new TestSuite("org.spaceroots.mantissa.functions.vectorial"); + + suite.addTest(VectorialValuedPairTest.suite()); + suite.addTest(ComputableFunctionSamplerTest.suite()); + suite.addTest(BasicSampledFunctionIteratorTest.suite()); + + return suite; + + } +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/functions/vectorial/BasicSampledFunctionIteratorTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/functions/vectorial/BasicSampledFunctionIteratorTest.java new file mode 100644 index 000000000..feaf6e409 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/functions/vectorial/BasicSampledFunctionIteratorTest.java @@ -0,0 +1,157 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.functions.vectorial; + +import org.spaceroots.mantissa.functions.FunctionException; +import org.spaceroots.mantissa.functions.ExhaustedSampleException; + +import junit.framework.*; + +public class BasicSampledFunctionIteratorTest + extends TestCase { + + public BasicSampledFunctionIteratorTest(String name) { + super(name); + } + + public void testIteration() + throws ExhaustedSampleException, FunctionException { + + BasicSampledFunctionIterator iter = + new BasicSampledFunctionIterator(new Function(0.0, 0.1, 10)); + + for (int i = 0; i < 10; ++i) { + assertTrue(iter.hasNext()); + VectorialValuedPair pair = iter.nextSamplePoint(); + assertTrue(Math.abs(pair.getX() - 0.1 * i) < 1.0e-10); + assertTrue(Math.abs(pair.getY()[0] + 0.1 * i) < 1.0e-10); + assertTrue(Math.abs(pair.getY()[1] + 0.2 * i) < 1.0e-10); + } + + } + + public void testExhausted() + throws ExhaustedSampleException, FunctionException { + + BasicSampledFunctionIterator iter = + new BasicSampledFunctionIterator(new Function(0.0, 0.1, 10)); + + for (int i = 0; i < 10; ++i) { + assertTrue(iter.hasNext()); + iter.nextSamplePoint(); + } + + assertTrue(! iter.hasNext()); + + boolean exceptionOccurred = false; + try { + iter.nextSamplePoint(); + } catch(ExhaustedSampleException e) { + exceptionOccurred = true; + } + assertTrue(exceptionOccurred); + + } + + public void testUnderlyingException() + throws ExhaustedSampleException, FunctionException { + + BasicSampledFunctionIterator iter = + new BasicSampledFunctionIterator(new SampledFunction() { + + private boolean fireException = false; + + public int size() { + return 2; + } + + public int getDimension() { + return 2; + } + + public VectorialValuedPair samplePointAt(int i) + throws FunctionException { + if (fireException) { + throw new FunctionException("boom"); + } + fireException = true; + return new VectorialValuedPair(0.0, null); + } + }); + + boolean exceptionOccurred = false; + try { + iter.nextSamplePoint(); + } catch(FunctionException e) { + exceptionOccurred = true; + } + assertTrue(! exceptionOccurred); + + exceptionOccurred = false; + try { + iter.nextSamplePoint(); + } catch(FunctionException e) { + exceptionOccurred = true; + } + assertTrue(exceptionOccurred); + + } + + public static Test suite() { + return new TestSuite(BasicSampledFunctionIteratorTest.class); + } + + private class Function + implements SampledFunction { + + private double begin; + private double step; + private int n; + private double[] values; + + public Function(double begin, double step, int n) { + this.begin = begin; + this.step = step; + this.n = n; + values = new double[2]; + } + + public int size() { + return n; + } + + public int getDimension() { + return 2; + } + + public VectorialValuedPair samplePointAt(int i) + throws FunctionException { + + if (i < 0 || i >= n) { + throw new FunctionException("outside of range"); + } + + double x = begin + i * step; + values[0] = -x; + values[1] = 2.0 * values[0]; + return new VectorialValuedPair(x, values); + + } + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/functions/vectorial/ComputableFunctionSamplerTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/functions/vectorial/ComputableFunctionSamplerTest.java new file mode 100644 index 000000000..a27131810 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/functions/vectorial/ComputableFunctionSamplerTest.java @@ -0,0 +1,221 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.functions.vectorial; + +import org.spaceroots.mantissa.functions.FunctionException; + +import junit.framework.*; + +public class ComputableFunctionSamplerTest + extends TestCase { + + public ComputableFunctionSamplerTest(String name) { + super(name); + } + + public void testBeginStepNumber() + throws FunctionException { + + ComputableFunctionSampler sampler = + new ComputableFunctionSampler(new Function(0.0, 1.0), 0.0, 0.099, 11); + + assertTrue(sampler.size() == 11); + assertTrue(sampler.getDimension() == 2); + assertTrue(Math.abs(sampler.samplePointAt(0).getX() - 0.000) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(0).getY()[0] + 0.000) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(0).getY()[1] + 0.000) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(5).getX() - 0.495) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(5).getY()[0] + 0.495) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(5).getY()[1] + 0.990) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(10).getX() - 0.990) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(10).getY()[0] + 0.990) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(10).getY()[1] + 1.980) < 1.0e-10); + + } + + public void testRangeNumber() + throws FunctionException { + + double[] range = new double[2]; + range[0] = 0.0; + range[1] = 1.0; + ComputableFunctionSampler sampler = + new ComputableFunctionSampler(new Function (0.0, 1.0), range, 11); + + assertTrue(sampler.size() == 11); + assertTrue(sampler.getDimension() == 2); + assertTrue(Math.abs(sampler.samplePointAt(0).getX() - 0.0) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(0).getY()[0] + 0.0) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(0).getY()[1] + 0.0) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(5).getX() - 0.5) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(5).getY()[0] + 0.5) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(5).getY()[1] + 1.0) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(10).getX() - 1.0) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(10).getY()[0] + 1.0) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(10).getY()[1] + 2.0) < 1.0e-10); + + } + + public void testRangeStepNoAdjust() + throws FunctionException { + + double[] range = new double[2]; + range[0] = 0.0; + range[1] = 1.0; + ComputableFunctionSampler sampler = + new ComputableFunctionSampler(new Function(0.0, 1.0), + range, 0.083, false); + + assertTrue(sampler.size() == 12); + assertTrue(sampler.getDimension() == 2); + assertTrue(Math.abs(sampler.samplePointAt(0).getX() - 0.000) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(0).getY()[0] + 0.000) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(0).getY()[1] + 0.000) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(5).getX() - 0.415) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(5).getY()[0] + 0.415) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(5).getY()[1] + 0.830) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(11).getX() - 0.913) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(11).getY()[0] + 0.913) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(11).getY()[1] + 1.826) < 1.0e-10); + + } + + public void testRangeStepAdjust() + throws FunctionException { + + double[] range = new double[2]; + range[0] = 0.0; + range[1] = 1.0; + ComputableFunctionSampler sampler = + new ComputableFunctionSampler(new Function(0.0, 1.0), + range, 0.083, true); + + assertTrue(sampler.size() == 13); + assertTrue(sampler.getDimension() == 2); + assertTrue(Math.abs(sampler.samplePointAt(0).getX() - 0.0) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(0).getY()[0] + 0.0) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(0).getY()[1] + 0.0) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(6).getX() - 0.5) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(6).getY()[0] + 0.5) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(6).getY()[1] + 1.0) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(12).getX() - 1.0) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(12).getY()[0] + 1.0) < 1.0e-10); + assertTrue(Math.abs(sampler.samplePointAt(12).getY()[1] + 2.0) < 1.0e-10); + + } + + public void testOutOfRange() + throws FunctionException { + + ComputableFunctionSampler sampler = + new ComputableFunctionSampler(new Function(0.0, 1.0), 0.0, 1.0, 10); + + boolean exceptionOccurred = false; + try { + sampler.samplePointAt(-1); + } catch(ArrayIndexOutOfBoundsException e) { + exceptionOccurred = true; + } + assertTrue(exceptionOccurred); + + exceptionOccurred = false; + try { + sampler.samplePointAt(10); + } catch(ArrayIndexOutOfBoundsException e) { + exceptionOccurred = true; + } + assertTrue(exceptionOccurred); + + } + + public void testUnderlyingException() { + + ComputableFunctionSampler sampler = + new ComputableFunctionSampler(new ComputableFunction() { + + public int getDimension() { + return 2; + } + + public double[] valueAt(double x) + throws FunctionException { + if (x < 0.5) { + double[] res = new double[2]; + res[0] = -x; + res[1] = -2.0 * x; + return res; + } + throw new FunctionException("upper half range exception"); + } + + }, + 0.0, 0.1, 11); + + boolean exceptionOccurred = false; + try { + sampler.samplePointAt(2); + } catch(FunctionException e) { + exceptionOccurred = true; + } + assertTrue(! exceptionOccurred); + + exceptionOccurred = false; + try { + sampler.samplePointAt(8); + } catch(FunctionException e) { + exceptionOccurred = true; + } + assertTrue(exceptionOccurred); + + } + + public static Test suite() { + return new TestSuite(ComputableFunctionSamplerTest.class); + } + + private class Function + implements ComputableFunction { + private double min; + private double max; + private double[] values; + + public int getDimension() { + return 2; + } + + public Function(double min, double max) { + this.min = min; + this.max = max; + values = new double[2]; + } + + public double[] valueAt(double x) + throws FunctionException { + + if (x < min || x > max) { + throw new FunctionException("outside of range"); + } + + values[0] = -x; + values[1] = -2.0 * x; + return values; + + } + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/functions/vectorial/VectorialValuedPairTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/functions/vectorial/VectorialValuedPairTest.java new file mode 100644 index 000000000..dd267bfc6 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/functions/vectorial/VectorialValuedPairTest.java @@ -0,0 +1,57 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.functions.vectorial; + +import junit.framework.*; + +public class VectorialValuedPairTest + extends TestCase { + + public VectorialValuedPairTest(String name) { + super(name); + } + + public void testConstructor() { + double[] tab = new double[2]; + tab[0] = -8.4; + tab[1] = -3.2; + VectorialValuedPair pair = new VectorialValuedPair(1.2, tab); + assertTrue(Math.abs(pair.getX() - 1.2) < 1.0e-10); + assertTrue(Math.abs(pair.getY()[0] + 8.4) < 1.0e-10); + assertTrue(Math.abs(pair.getY()[1] + 3.2) < 1.0e-10); + } + + public void testCopyConstructor() { + double[] tab = new double[2]; + tab[0] = -8.4; + tab[1] = -3.2; + VectorialValuedPair pair1 = new VectorialValuedPair(1.2, tab); + VectorialValuedPair pair2 = new VectorialValuedPair(pair1); + assertTrue(Math.abs(pair2.getX() - pair1.getX()) < 1.0e-10); + assertTrue(Math.abs(pair2.getY()[0] - pair1.getY()[0]) < 1.0e-10); + assertTrue(Math.abs(pair2.getY()[1] - pair1.getY()[1]) < 1.0e-10); + assertTrue(Math.abs(pair2.getX() - 1.2) < 1.0e-10); + assertTrue(Math.abs(pair2.getY()[0] + 8.4) < 1.0e-10); + assertTrue(Math.abs(pair2.getY()[1] + 3.2) < 1.0e-10); + } + + public static Test suite() { + return new TestSuite(VectorialValuedPairTest.class); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/geometry/AllTests.java b/src/mantissa/tests-src/org/spaceroots/mantissa/geometry/AllTests.java new file mode 100644 index 000000000..e1ce528cc --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/geometry/AllTests.java @@ -0,0 +1,35 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.geometry; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class AllTests { + public static Test suite() { + + TestSuite suite = new TestSuite("org.spaceroots.mantissa.geometry"); + + suite.addTest(Vector3DTest.suite()); + suite.addTest(ImmutableVector3DTest.suite()); + suite.addTest(RotationTest.suite()); + + return suite; + + } +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/geometry/ImmutableVector3DTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/geometry/ImmutableVector3DTest.java new file mode 100644 index 000000000..b378ef0c9 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/geometry/ImmutableVector3DTest.java @@ -0,0 +1,43 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.geometry; + +import junit.framework.*; + +public class ImmutableVector3DTest + extends TestCase { + + public ImmutableVector3DTest(String name) { + super(name); + } + + public void testCanonical() { + try { + Vector3D.plusK.normalizeSelf(); + fail("an exception should have been thrown"); + } catch (UnsupportedOperationException uoe) { + } catch (Exception e) { + fail ("wrong exception caught"); + } + } + + public static Test suite() { + return new TestSuite(ImmutableVector3DTest.class); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/geometry/RotationTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/geometry/RotationTest.java new file mode 100644 index 000000000..7f5bf80fd --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/geometry/RotationTest.java @@ -0,0 +1,344 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.geometry; + +import junit.framework.*; + +public class RotationTest + extends TestCase { + + public RotationTest(String name) { + super(name); + } + + public void testIdentity() { + + Rotation r = new Rotation(); + checkVector(r.applyTo(Vector3D.plusI), Vector3D.plusI); + checkVector(r.applyTo(Vector3D.plusJ), Vector3D.plusJ); + checkVector(r.applyTo(Vector3D.plusK), Vector3D.plusK); + checkAngle(r.getAngle(), 0); + + r = new Rotation(-1, 0, 0, 0, false); + checkVector(r.applyTo(Vector3D.plusI), Vector3D.plusI); + checkVector(r.applyTo(Vector3D.plusJ), Vector3D.plusJ); + checkVector(r.applyTo(Vector3D.plusK), Vector3D.plusK); + checkAngle(r.getAngle(), 0); + + r = new Rotation(42, 0, 0, 0, true); + checkVector(r.applyTo(Vector3D.plusI), Vector3D.plusI); + checkVector(r.applyTo(Vector3D.plusJ), Vector3D.plusJ); + checkVector(r.applyTo(Vector3D.plusK), Vector3D.plusK); + checkAngle(r.getAngle(), 0); + + } + + public void testAxisAngle() { + + Rotation r = new Rotation(new Vector3D(10, 10, 10), 2 * Math.PI / 3); + checkVector(r.applyTo(Vector3D.plusI), Vector3D.plusJ); + checkVector(r.applyTo(Vector3D.plusJ), Vector3D.plusK); + checkVector(r.applyTo(Vector3D.plusK), Vector3D.plusI); + double s = 1 / Math.sqrt(3); + checkVector(r.getAxis(), new Vector3D(s, s, s)); + checkAngle(r.getAngle(), 2 * Math.PI / 3); + + try { + r = new Rotation(new Vector3D(0, 0, 0), 2 * Math.PI / 3); + fail("an exception should have been thrown"); + } catch (ArithmeticException e) { + } catch (Exception e) { + fail("unexpected exception"); + } + + r = new Rotation(Vector3D.plusK, 1.5 * Math.PI); + checkVector(r.getAxis(), new Vector3D(0, 0, -1)); + checkAngle(r.getAngle(), 0.5 * Math.PI); + + r = new Rotation(Vector3D.plusJ, Math.PI); + checkVector(r.getAxis(), Vector3D.plusJ); + checkAngle(r.getAngle(), Math.PI); + + } + + public void testVectorOnePair() { + + Vector3D u = new Vector3D(3, 2, 1); + Vector3D v = new Vector3D(-4, 2, 2); + Rotation r = new Rotation(u, v); + checkVector(r.applyTo(Vector3D.multiply(v.getNorm(), u)), + Vector3D.multiply(u.getNorm(), v)); + + checkAngle(new Rotation(u, Vector3D.negate(u)).getAngle(), Math.PI); + + } + + public void testVectorTwoPairs() { + + Vector3D u1 = new Vector3D(3, 0, 0); + Vector3D u2 = new Vector3D(0, 5, 0); + Vector3D v1 = new Vector3D(0, 0, 2); + Vector3D v2 = new Vector3D(-2, 0, 2); + Rotation r = new Rotation(u1, u2, v1, v2); + checkVector(r.applyTo(Vector3D.plusI), Vector3D.plusK); + checkVector(r.applyTo(Vector3D.plusJ), Vector3D.negate(Vector3D.plusI)); + + r = new Rotation(u1, u2, Vector3D.negate(u1), Vector3D.negate(u2)); + Vector3D axis = r.getAxis(); + if (Vector3D.dotProduct(axis, Vector3D.plusK) > 0) { + checkVector(axis, Vector3D.plusK); + } else { + checkVector(axis, Vector3D.negate(Vector3D.plusK)); + } + checkAngle(r.getAngle(), Math.PI); + + } + + public void testMatrix() + throws NotARotationMatrixException { + + double[][] m1 = { { 0.0, 1.0, 0.0 }, + { 0.0, 0.0, 1.0 }, + { 1.0, 0.0, 0.0 } }; + Rotation r = new Rotation(m1, 1.0e-7); + checkVector(r.applyTo(Vector3D.plusI), Vector3D.plusK); + checkVector(r.applyTo(Vector3D.plusJ), Vector3D.plusI); + checkVector(r.applyTo(Vector3D.plusK), Vector3D.plusJ); + + double[][] m2 = { { 0.83203, -0.55012, -0.07139 }, + { 0.48293, 0.78164, -0.39474 }, + { 0.27296, 0.29396, 0.91602 } }; + r = new Rotation(m2, 1.0e-12); + + double[][] m3 = r.getMatrix(); + double d00 = m2[0][0] - m3[0][0]; + double d01 = m2[0][1] - m3[0][1]; + double d02 = m2[0][2] - m3[0][2]; + double d10 = m2[1][0] - m3[1][0]; + double d11 = m2[1][1] - m3[1][1]; + double d12 = m2[1][2] - m3[1][2]; + double d20 = m2[2][0] - m3[2][0]; + double d21 = m2[2][1] - m3[2][1]; + double d22 = m2[2][2] - m3[2][2]; + + assertTrue(Math.abs(d00) < 6.0e-6); + assertTrue(Math.abs(d01) < 6.0e-6); + assertTrue(Math.abs(d02) < 6.0e-6); + assertTrue(Math.abs(d10) < 6.0e-6); + assertTrue(Math.abs(d11) < 6.0e-6); + assertTrue(Math.abs(d12) < 6.0e-6); + assertTrue(Math.abs(d20) < 6.0e-6); + assertTrue(Math.abs(d21) < 6.0e-6); + assertTrue(Math.abs(d22) < 6.0e-6); + + assertTrue(Math.abs(d00) > 4.0e-7); + assertTrue(Math.abs(d01) > 4.0e-7); + assertTrue(Math.abs(d02) > 4.0e-7); + assertTrue(Math.abs(d10) > 4.0e-7); + assertTrue(Math.abs(d11) > 4.0e-7); + assertTrue(Math.abs(d12) > 4.0e-7); + assertTrue(Math.abs(d20) > 4.0e-7); + assertTrue(Math.abs(d21) > 4.0e-7); + assertTrue(Math.abs(d22) > 4.0e-7); + + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { + double m3tm3 = m3[i][0] * m3[j][0] + + m3[i][1] * m3[j][1] + + m3[i][2] * m3[j][2]; + if (i == j) { + assertTrue(Math.abs(m3tm3 - 1.0) < 1.0e-10); + } else { + assertTrue(Math.abs(m3tm3) < 1.0e-10); + } + } + } + + checkVector(r.applyTo(Vector3D.plusI), + new Vector3D(m3[0][0], m3[1][0], m3[2][0])); + checkVector(r.applyTo(Vector3D.plusJ), + new Vector3D(m3[0][1], m3[1][1], m3[2][1])); + checkVector(r.applyTo(Vector3D.plusK), + new Vector3D(m3[0][2], m3[1][2], m3[2][2])); + + double[][] m4 = { { 1.0, 0.0, 0.0 }, + { 0.0, -1.0, 0.0 }, + { 0.0, 0.0, -1.0 } }; + r = new Rotation(m4, 1.0e-7); + checkAngle(r.getAngle(), Math.PI); + + try { + double[][] m5 = { { 0.0, 0.0, 1.0 }, + { 0.0, 1.0, 0.0 }, + { 1.0, 0.0, 0.0 } }; + r = new Rotation(m5, 1.0e-7); + fail("an exception should have been thrown"); + } catch (NotARotationMatrixException e) { + } catch (Exception e) { + fail("wrong exception caught"); + } + + } + + public void testAngles() + throws CardanEulerSingularityException { + + RotationOrder[] CardanOrders = { + RotationOrder.XYZ, RotationOrder.XZY, RotationOrder.YXZ, + RotationOrder.YZX, RotationOrder.ZXY, RotationOrder.ZYX + }; + + RotationOrder[] EulerOrders = { + RotationOrder.XYX, RotationOrder.XZX, RotationOrder.YXY, + RotationOrder.YZY, RotationOrder.ZXZ, RotationOrder.ZYZ + }; + + for (int i = 0; i < CardanOrders.length; ++i) { + for (double alpha1 = 0.1; alpha1 < 6.2; alpha1 += 0.3) { + for (double alpha2 = -1.55; alpha2 < 1.55; alpha2 += 0.3) { + for (double alpha3 = 0.1; alpha3 < 6.2; alpha3 += 0.3) { + Rotation r = new Rotation(CardanOrders[i], + alpha1, alpha2, alpha3); + double[] angles = r.getAngles(CardanOrders[i]); + checkAngle(angles[0], alpha1); + checkAngle(angles[1], alpha2); + checkAngle(angles[2], alpha3); + } + } + } + } + + for (int i = 0; i < EulerOrders.length; ++i) { + for (double alpha1 = 0.1; alpha1 < 6.2; alpha1 += 0.3) { + for (double alpha2 = 0.05; alpha2 < 3.1; alpha2 += 0.3) { + for (double alpha3 = 0.1; alpha3 < 6.2; alpha3 += 0.3) { + Rotation r = new Rotation(EulerOrders[i], + alpha1, alpha2, alpha3); + double[] angles = r.getAngles(EulerOrders[i]); + checkAngle(angles[0], alpha1); + checkAngle(angles[1], alpha2); + checkAngle(angles[2], alpha3); + } + } + } + } + + } + + public void testQuaternion() { + Rotation r1 = new Rotation(new Vector3D(2, -3, 5), 1.7); + double n = 23.5; + Rotation r2 = new Rotation(n * r1.getQ0(), n * r1.getQ1(), + n * r1.getQ2(), n * r1.getQ3(), + true); + for (double x = -0.9; x < 0.9; x += 0.2) { + for (double y = -0.9; y < 0.9; y += 0.2) { + for (double z = -0.9; z < 0.9; z += 0.2) { + Vector3D u = new Vector3D(x, y, z); + checkVector(r2.applyTo(u), r1.applyTo(u)); + } + } + } + } + + public void testCompose() { + + Rotation r1 = new Rotation(new Vector3D(2, -3, 5), 1.7); + Rotation r2 = new Rotation(new Vector3D(-1, 3, 2), 0.3); + Rotation r3 = r2.applyTo(r1); + + for (double x = -0.9; x < 0.9; x += 0.2) { + for (double y = -0.9; y < 0.9; y += 0.2) { + for (double z = -0.9; z < 0.9; z += 0.2) { + Vector3D u = new Vector3D(x, y, z); + checkVector(r2.applyTo(r1.applyTo(u)), r3.applyTo(u)); + } + } + } + + } + + public void testComposeInverse() { + + Rotation r1 = new Rotation(new Vector3D(2, -3, 5), 1.7); + Rotation r2 = new Rotation(new Vector3D(-1, 3, 2), 0.3); + Rotation r3 = r2.applyInverseTo(r1); + + for (double x = -0.9; x < 0.9; x += 0.2) { + for (double y = -0.9; y < 0.9; y += 0.2) { + for (double z = -0.9; z < 0.9; z += 0.2) { + Vector3D u = new Vector3D(x, y, z); + checkVector(r2.applyInverseTo(r1.applyTo(u)), r3.applyTo(u)); + } + } + } + + } + + public void testApplyInverseTo() { + + Rotation r = new Rotation(new Vector3D(2, -3, 5), 1.7); + for (double lambda = 0; lambda < 6.2; lambda += 0.2) { + for (double phi = -1.55; phi < 1.55; phi += 0.2) { + Vector3D u = new Vector3D(Math.cos(lambda) * Math.cos(phi), + Math.sin(lambda) * Math.cos(phi), + Math.sin(phi)); + r.applyInverseTo(r.applyTo(u)); + checkVector(u, r.applyInverseTo(r.applyTo(u))); + checkVector(u, r.applyTo(r.applyInverseTo(u))); + } + } + + r = new Rotation(); + for (double lambda = 0; lambda < 6.2; lambda += 0.2) { + for (double phi = -1.55; phi < 1.55; phi += 0.2) { + Vector3D u = new Vector3D(Math.cos(lambda) * Math.cos(phi), + Math.sin(lambda) * Math.cos(phi), + Math.sin(phi)); + checkVector(u, r.applyInverseTo(r.applyTo(u))); + checkVector(u, r.applyTo(r.applyInverseTo(u))); + } + } + + r = new Rotation(Vector3D.plusK, Math.PI); + for (double lambda = 0; lambda < 6.2; lambda += 0.2) { + for (double phi = -1.55; phi < 1.55; phi += 0.2) { + Vector3D u = new Vector3D(Math.cos(lambda) * Math.cos(phi), + Math.sin(lambda) * Math.cos(phi), + Math.sin(phi)); + checkVector(u, r.applyInverseTo(r.applyTo(u))); + checkVector(u, r.applyTo(r.applyInverseTo(u))); + } + } + + } + + private void checkVector(Vector3D v1, Vector3D v2) { + assertTrue(Vector3D.subtract(v1, v2).getNorm() < 1.0e-10); + } + + private void checkAngle(double a1, double a2) { + a2 -= 2 * Math.PI * Math.floor((a2 + Math.PI - a1) / (2 * Math.PI)); + assertTrue(Math.abs(a1 - a2) < 1.0e-10); + } + + public static Test suite() { + return new TestSuite(RotationTest.class); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/geometry/Vector3DTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/geometry/Vector3DTest.java new file mode 100644 index 000000000..576379fea --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/geometry/Vector3DTest.java @@ -0,0 +1,123 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.geometry; + +import junit.framework.*; + +public class Vector3DTest + extends TestCase { + + public Vector3DTest(String name) { + super(name); + } + + public void testCoordinates() { + Vector3D v = new Vector3D(1, 2, 3); + assertTrue(Math.abs(v.getX() - 1) < 1.0e-12); + assertTrue(Math.abs(v.getY() - 2) < 1.0e-12); + assertTrue(Math.abs(v.getZ() - 3) < 1.0e-12); + } + + public void testNorm() { + assertTrue(Math.abs(new Vector3D().getNorm()) < 1.0e-12); + assertTrue(Math.abs(new Vector3D(1, 2, 3).getNorm() - Math.sqrt(14)) + < 1.0e-12); + } + + public void testSubtract() { + + Vector3D v1 = new Vector3D(1, 2, 3); + Vector3D v2 = new Vector3D(-3, -2, -1); + v1.subtractFromSelf(v2); + checkVector(v1, new Vector3D(4, 4, 4)); + + checkVector(Vector3D.subtract(v2, v1), new Vector3D(-7, -6, -5)); + + } + + public void testAdd() { + Vector3D v1 = new Vector3D(1, 2, 3); + Vector3D v2 = new Vector3D(-3, -2, -1); + v1.addToSelf(v2); + checkVector(v1, new Vector3D(-2, 0, 2)); + + checkVector(Vector3D.add(v2, v1), new Vector3D(-5, -2, 1)); + + } + + public void testScalarProduct() { + Vector3D v = new Vector3D(1, 2, 3); + v.multiplySelf(3); + checkVector(v, new Vector3D(3, 6, 9)); + + checkVector(Vector3D.multiply(0.5, v), new Vector3D(1.5, 3, 4.5)); + + } + + public void testVectorialProducts() { + Vector3D v1 = new Vector3D(2, 1, -4); + Vector3D v2 = new Vector3D(3, 1, -1); + + assertTrue(Math.abs(Vector3D.dotProduct(v1, v2) - 11) < 1.0e-12); + + Vector3D v3 = Vector3D.crossProduct(v1, v2); + checkVector(v3, new Vector3D(3, -10, -1)); + + assertTrue(Math.abs(Vector3D.dotProduct(v1, v3)) < 1.0e-12); + assertTrue(Math.abs(Vector3D.dotProduct(v2, v3)) < 1.0e-12); + + } + + public void testAngular() { + + assertEquals(0, Vector3D.plusI.getAlpha(), 1.0e-10); + assertEquals(0, Vector3D.plusI.getDelta(), 1.0e-10); + assertEquals(Math.PI / 2, Vector3D.plusJ.getAlpha(), 1.0e-10); + assertEquals(0, Vector3D.plusJ.getDelta(), 1.0e-10); + assertEquals(0, Vector3D.plusK.getAlpha(), 1.0e-10); + assertEquals(Math.PI / 2, Vector3D.plusK.getDelta(), 1.0e-10); + + Vector3D u = new Vector3D(-1, 1, -1); + assertEquals(3 * Math.PI /4, u.getAlpha(), 1.0e-10); + assertEquals(-1.0 / Math.sqrt(3), Math.sin(u.getDelta()), 1.0e-10); + + } + + public void testAngularSeparation() { + Vector3D v1 = new Vector3D(2, -1, 4); + + Vector3D k = v1; + k.normalizeSelf(); + Vector3D i = k.orthogonal(); + + Vector3D v2 = Vector3D.multiply(Math.cos(1.2), k); + v2.addToSelf(Vector3D.multiply(Math.sin(1.2), i)); + + assertTrue(Math.abs(Vector3D.angle(v1, v2) - 1.2) < 1.0e-12); + + } + + private void checkVector(Vector3D v1, Vector3D v2) { + assertTrue(Vector3D.subtract(v1, v2).getNorm() < 1.0e-12); + } + + public static Test suite() { + return new TestSuite(Vector3DTest.class); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/linalg/AllTests.java b/src/mantissa/tests-src/org/spaceroots/mantissa/linalg/AllTests.java new file mode 100644 index 000000000..b30bba28e --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/linalg/AllTests.java @@ -0,0 +1,40 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.linalg; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class AllTests { + public static Test suite() { + + TestSuite suite = new TestSuite("org.spaceroots.mantissa.linalg"); + + suite.addTest(NonNullRangeTest.suite()); + suite.addTest(GeneralMatrixTest.suite()); + suite.addTest(DiagonalMatrixTest.suite()); + suite.addTest(LowerTriangularMatrixTest.suite()); + suite.addTest(UpperTriangularMatrixTest.suite()); + suite.addTest(GeneralSquareMatrixTest.suite()); + suite.addTest(SymetricalMatrixTest.suite()); + suite.addTest(MatrixFactoryTest.suite()); + + return suite; + + } +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/linalg/DiagonalMatrixTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/linalg/DiagonalMatrixTest.java new file mode 100644 index 000000000..a4446092e --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/linalg/DiagonalMatrixTest.java @@ -0,0 +1,190 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.linalg; + +import junit.framework.*; + +public class DiagonalMatrixTest + extends TestCase { + + public DiagonalMatrixTest(String name) { + super(name); + } + + public void testConstantDiagonal() { + checkMatrix(new DiagonalMatrix(5, 2.7), 2.7); + } + + public void testNoSetOutsideOfDiagonal() { + + DiagonalMatrix d = new DiagonalMatrix(4); + + for (int i = 0; i < d.getRows(); ++i) { + for (int j = 0; j < d.getColumns(); ++j) { + if (i == j) { + d.setElement(i, j, 2.7); + } else { + boolean gotIt = false; + try { + d.setElement(i, j, -1.3); + } catch (ArrayIndexOutOfBoundsException e) { + gotIt = true; + } + assertTrue(gotIt); + } + } + } + + checkMatrix(d, 2.7); + + } + + public void testCopy() { + DiagonalMatrix d1 = new DiagonalMatrix(7, 4.3); + DiagonalMatrix d2 = new DiagonalMatrix(d1); + + for (int i = 0; i < d1.getRows(); ++i) { + d1.setElement(i, i, -1.0); + } + + assertTrue(d2.getRows() == d1.getRows()); + assertTrue(d2.getColumns() == d1.getColumns()); + + checkMatrix(d2, 4.3); + + } + + public void testDuplicate() { + DiagonalMatrix d1 = new DiagonalMatrix(6, -8.8); + + Matrix d2 = d1.duplicate(); + assertTrue(d2 instanceof DiagonalMatrix); + + for (int i = 0; i < d1.getRows(); ++i) { + d1.setElement(i, i, -1.0); + } + + assertTrue(d2.getRows() == d1.getRows()); + assertTrue(d2.getColumns() == d1.getColumns()); + + checkMatrix(d2, -8.8); + + } + + public void testTranspose() { + + DiagonalMatrix d = new DiagonalMatrix(5, 3.4); + + Matrix transposed = d.getTranspose(); + assertTrue(transposed instanceof DiagonalMatrix); + + checkMatrix(transposed, 3.4); + + } + + public void testDeterminant() { + + double expected; + + expected = 1.0; + for (int k = 1; k < 10; ++k) { + expected *= 2; + DiagonalMatrix d = new DiagonalMatrix(k, 2.0); + assertTrue(Math.abs(d.getDeterminant(1.0e-10) - expected) < 1.0e-10); + } + + expected = 1.0; + for (int k = 1; k < 10; ++k) { + expected *= k; + DiagonalMatrix d = new DiagonalMatrix(k); + for (int i = 0; i < k; ++i) { + d.setElement(i, i, i + 1); + } + assertTrue(Math.abs(d.getDeterminant(1.0e-10) - expected) < 1.0e-10); + } + + } + + public void testSolve() + throws SingularMatrixException { + + DiagonalMatrix d = new DiagonalMatrix(6); + for (int i = 0; i < d.getRows(); ++i) { + d.setElement(i, i, i + 1.0); + } + + GeneralMatrix b = new GeneralMatrix(6, 3); + for (int i = 0; i < b.getRows(); ++i) { + b.setElement(i, 0, i + 1.0); + b.setElement(i, 1, (i + 1.0) * (i + 1.0)); + b.setElement(i, 2, 0.0); + } + + Matrix result = d.solve(b, 1.0e-10); + + assertTrue(result.getRows() == b.getRows()); + assertTrue(result.getColumns() == b.getColumns()); + + for (int i = 0; i < result.getRows(); ++i) { + assertTrue(Math.abs(result.getElement(i, 0) - 1.0) < 1.0e-10); + assertTrue(Math.abs(result.getElement(i, 1) - (i + 1.0)) < 1.0e-10); + assertTrue(Math.abs(result.getElement(i, 2) - 0.0) < 1.0e-10); + } + + boolean gotIt = false; + try { + d.setElement(3, 3, 0.0); + result = d.solve(b, 1.0e-10); + } catch (SingularMatrixException e) { + gotIt = true; + } + assertTrue(gotIt); + + } + + public void testInverse() + throws SingularMatrixException { + + DiagonalMatrix d = new DiagonalMatrix(4); + for (int i = 0; i < d.getRows (); ++i) { + d.setElement(i, i, i + 1.0); + } + + Matrix inverse = d.getInverse(1.0e-10); + assertTrue(inverse instanceof DiagonalMatrix); + + for (int i = 0; i < inverse.getRows(); ++i) { + assertTrue(Math.abs(inverse.getElement(i, i) - 1.0 / (i + 1.0)) < 1.0e-10); + } + + } + + public static Test suite() { + return new TestSuite(DiagonalMatrixTest.class); + } + + public void checkMatrix(Matrix d, double value) { + for (int i = 0; i < d.getRows(); ++i) { + for (int j = 0; j < d.getColumns(); ++j) { + double expected = (i == j) ? value : 0.0; + assertTrue(Math.abs(d.getElement(i, j) - expected) < 1.0e-10); + } + } + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/linalg/GeneralMatrixTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/linalg/GeneralMatrixTest.java new file mode 100644 index 000000000..dd92e9177 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/linalg/GeneralMatrixTest.java @@ -0,0 +1,373 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.linalg; + +import junit.framework.*; + +public class GeneralMatrixTest + extends TestCase { + + public GeneralMatrixTest(String name) { + super(name); + } + + public void testDimensions() { + GeneralMatrix m = new GeneralMatrix(3, 4); + assertTrue(m.getRows() == 3); + assertTrue(m.getColumns() == 4); + } + + public void testInvalidDimensions() { + boolean gotIt; + + gotIt = false; + try { + new GeneralMatrix(0, 2); + } catch(IllegalArgumentException e) { + gotIt = true; + } + assertTrue(gotIt); + + gotIt = false; + try { + new GeneralMatrix(1, -3, null); + } catch(IllegalArgumentException e) { + gotIt = true; + } + assertTrue(gotIt); + + } + + public void testElements() { + Matrix m = buildMatrix(5, 10, new ElementPattern() { + public double value(int i, int j) { + return i + 0.01 * j; + } + }); + + checkMatrix(m, new ElementPattern() { + public double value(int i, int j) { + return i + 0.01 * j; + } + }); + + } + + public void testCopy() { + Matrix m1 = buildMatrix(5, 10, new ElementPattern() { + public double value(int i, int j) { + return i + 0.01 * j; + } + }); + + GeneralMatrix m2 = new GeneralMatrix(m1); + + for (int i = 0; i < m1.getRows(); ++i) { + for (int j = 0; j < m1.getColumns(); ++j) { + m1.setElement(i, j, -1.0); + } + } + + assertTrue(m2.getRows() == m1.getRows()); + assertTrue(m2.getColumns() == m1.getColumns()); + + checkMatrix(m2, new ElementPattern() { + public double value(int i, int j) { + return i + 0.01 * j; + } + }); + + } + + public void testDuplicate() { + Matrix m1 = buildMatrix(5, 10, new ElementPattern() { + public double value(int i, int j) { + return i + 0.01 * j; + } + }); + + Matrix m2 = m1.duplicate(); + assertTrue(m2 instanceof GeneralMatrix); + + for (int i = 0; i < m1.getRows(); ++i) { + for (int j = 0; j < m1.getColumns(); ++j) { + m1.setElement(i, j, -1.0); + } + } + + assertTrue(m2.getRows() == m1.getRows()); + assertTrue(m2.getColumns() == m1.getColumns()); + + checkMatrix (m2, new ElementPattern() { + public double value(int i, int j) { + return i + 0.01 * j; + } + }); + + } + + public void testAddKO() { + boolean gotIt = false; + try { + new GeneralMatrix(2, 3).add(new GeneralMatrix(3, 2)); + } catch(IllegalArgumentException e) { + gotIt = true; + } + assertTrue(gotIt); + } + + public void testAddOK() { + + Matrix m1 = buildMatrix(5, 10, new ElementPattern() { + public double value(int i, int j) { + return i + 0.01 * j; + } + }); + + Matrix m2 = buildMatrix(m1.getRows(), + m1.getColumns(), + new ElementPattern() { + public double value(int i, int j) { + return 100 * i - 0.01 * j; + } + }); + + Matrix m3 = m1.add(m2); + + checkMatrix(m3, new ElementPattern() { + public double value(int i, int j) { + return 101 * i; + } + }); + + } + + public void testSelfAdd() { + + GeneralMatrix m1 = buildMatrix(5, 10, new ElementPattern() { + public double value(int i, int j) { + return i + 0.01 * j; + } + }); + + Matrix m2 = buildMatrix(m1.getRows(), + m1.getColumns(), + new ElementPattern() { + public double value(int i, int j) { + return 100 * i - 0.01 * j; + } + }); + + m1.selfAdd(m2); + + checkMatrix(m1, new ElementPattern() { + public double value(int i, int j) { + return 101 * i; + } + }); + + } + + public void testSubKO() { + boolean gotIt = false; + try { + new GeneralMatrix(2, 3).sub(new GeneralMatrix(3, 2)); + } catch(IllegalArgumentException e) { + gotIt = true; + } + assertTrue(gotIt); + } + + public void testSubOK() { + + Matrix m1 = buildMatrix(5, 10, new ElementPattern() { + public double value(int i, int j) { + return i + 0.01 * j; + } + }); + + Matrix m2 = buildMatrix(m1.getRows(), + m1.getColumns(), + new ElementPattern() { + public double value(int i, int j) { + return 100 * i - 0.01 * j; + } + }); + + Matrix m3 = m1.sub(m2); + + checkMatrix(m3, new ElementPattern() { + public double value(int i, int j) { + return 0.02 * j - 99 * i; + } + }); + + } + + public void testSelfSub() { + + GeneralMatrix m1 = buildMatrix(5, 10, new ElementPattern() { + public double value(int i, int j) { + return i + 0.01 * j; + } + }); + + Matrix m2 = buildMatrix(m1.getRows(), + m1.getColumns(), + new ElementPattern() { + public double value(int i, int j) { + return 100 * i - 0.01 * j; + } + }); + + m1.selfSub(m2); + + checkMatrix(m1, new ElementPattern() { + public double value(int i, int j) { + return 0.02 * j - 99 * i; + } + }); + + } + + public void testMulMKO() { + boolean gotIt = false; + try { + new GeneralMatrix(2, 3).mul(new GeneralMatrix(2, 3)); + } catch(IllegalArgumentException e) { + gotIt = true; + } + assertTrue(gotIt); + } + + public void testMulMOK() { + + Matrix m1 = buildMatrix(5, 10, new ElementPattern() { + public double value(int i, int j) { + return i + 0.01 * j; + } + }); + + Matrix m2 = buildMatrix(m1.getColumns(), 4, new ElementPattern() { + public double value(int i, int j) { + return 2 * i - j; + } + }); + + Matrix m3 = m1.mul(m2); + + checkMatrix(m3, new ElementPattern() { + public double value(int i, int j) { + int p = 10; // must be equal to m1.getColumns() + return p * ((2 * i - 0.01 *j) * (p - 1) / 2.0 + - i* j + + (p - 1) * (2 * p - 1) / 300.0); + } + }); + + } + + public void testMulD() { + + Matrix m1 = buildMatrix(5, 10, new ElementPattern() { + public double value(int i, int j) { + return i + 0.01 * j; + } + }); + + Matrix m2 = m1.mul(2.5); + + checkMatrix(m2, new ElementPattern() { + public double value(int i, int j) { + return 2.5 * (i + 0.01 * j); + } + }); + + } + + public void testSelfMul() { + + Matrix m = buildMatrix(5, 10, new ElementPattern() { + public double value(int i, int j) { + return i + 0.01 * j; + } + }); + + m.selfMul(2.5); + + checkMatrix(m, new ElementPattern() { + public double value(int i, int j) { + return 2.5 * (i + 0.01 * j); + } + }); + + } + + public void testTranspose() { + + Matrix m1 = buildMatrix(5, 10, new ElementPattern() { + public double value(int i, int j) { + return i + 0.01 * j; + } + }); + + Matrix m2 = m1.getTranspose(); + + assertTrue(m1.getRows() == m2.getColumns()); + assertTrue(m1.getColumns() == m2.getRows()); + + checkMatrix(m2, new ElementPattern() { + public double value(int i, int j) { + return 0.01 * i + j; + } + }); + + } + + public static Test suite() { + return new TestSuite(GeneralMatrixTest.class); + } + + public interface ElementPattern { + public double value(int i, int j); + } + + public GeneralMatrix buildMatrix(int rows, int columns, + ElementPattern pattern) { + GeneralMatrix m = new GeneralMatrix(rows, columns); + + for (int i = 0; i < m.getRows(); ++i) { + for (int j = 0; j < m.getColumns(); ++j){ + m.setElement(i, j, pattern.value(i, j)); + } + } + + return m; + + } + + public void checkMatrix(Matrix m, ElementPattern pattern) { + for (int i = 0; i < m.getRows(); ++i) { + for (int j = 0; j < m.getColumns(); ++j) { + assertTrue(Math.abs(m.getElement(i, j) - pattern.value(i, j)) + < 1.0e-10); + } + } + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/linalg/GeneralSquareMatrixTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/linalg/GeneralSquareMatrixTest.java new file mode 100644 index 000000000..d5885cfed --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/linalg/GeneralSquareMatrixTest.java @@ -0,0 +1,402 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.linalg; + +import junit.framework.*; + +public class GeneralSquareMatrixTest + extends TestCase { + + public GeneralSquareMatrixTest(String name) { + super(name); + } + + public void testDimensions() { + GeneralSquareMatrix m = new GeneralSquareMatrix(3); + assertTrue(m.getRows() == 3); + assertTrue(m.getColumns() == 3); + } + + public void testInvalidDimensions() { + boolean gotIt; + + gotIt = false; + try { + new GeneralSquareMatrix(0); + } catch(IllegalArgumentException e) { + gotIt = true; + } + assertTrue(gotIt); + + gotIt = false; + try { + new GeneralSquareMatrix(-3, null); + } catch(IllegalArgumentException e) { + gotIt = true; + } + assertTrue(gotIt); + + } + + public void testElements() { + Matrix m = buildMatrix(5, new ElementPattern() { + public double value(int i, int j) { + return i + 0.01 * j; + } + }); + + checkMatrix(m, new ElementPattern() { + public double value(int i, int j) { + return i + 0.01 * j; + } + }); + + } + + public void testCopy() { + GeneralSquareMatrix m1 = buildMatrix(5, new ElementPattern() { + public double value(int i, int j) { + return i + 0.01 * j; + } + }); + + GeneralSquareMatrix m2 = new GeneralSquareMatrix(m1); + + for (int i = 0; i < m1.getRows(); ++i) { + for (int j = 0; j < m1.getColumns(); ++j) { + m1.setElement(i, j, -1.0); + } + } + + assertTrue(m2.getRows() == m1.getRows()); + assertTrue(m2.getColumns() == m1.getColumns()); + + checkMatrix(m2, new ElementPattern() { + public double value(int i, int j) { + return i + 0.01 * j; + } + }); + + } + + public void testDuplicate() { + GeneralSquareMatrix m1 = buildMatrix(5, new ElementPattern() { + public double value(int i, int j) { + return i + 0.01 * j; + } + }); + + Matrix m2 = m1.duplicate(); + assertTrue(m2 instanceof GeneralSquareMatrix); + + for (int i = 0; i < m1.getRows(); ++i) { + for (int j = 0; j < m1.getColumns(); ++j) { + m1.setElement(i, j, -1.0); + } + } + + assertTrue(m2.getRows() == m1.getRows()); + assertTrue(m2.getColumns() == m1.getColumns()); + + checkMatrix(m2, new ElementPattern() { + public double value(int i, int j) { + return i + 0.01 * j; + } + }); + + } + + public void testSelfAdd() { + GeneralSquareMatrix m1 = buildMatrix(5, new ElementPattern() { + public double value(int i, int j) { + return i + 0.01 * j; + } + }); + + GeneralSquareMatrix m2 = buildMatrix(5, new ElementPattern() { + public double value(int i, int j) { + return 2 * i - 0.03 * j; + } + }); + + + m1.selfAdd(m2); + + checkMatrix(m1, new ElementPattern() { + public double value(int i, int j) { + return 3 * i - 0.02 * j; + } + }); + + } + + public void testSelfSub() { + GeneralSquareMatrix m1 = buildMatrix(5, new ElementPattern() { + public double value(int i, int j) { + return i + 0.01 * j; + } + }); + + GeneralSquareMatrix m2 = buildMatrix(5, new ElementPattern() { + public double value(int i, int j) { + return 2 * i - 0.03 * j; + } + }); + + + m1.selfSub(m2); + + checkMatrix(m1, new ElementPattern() { + public double value(int i, int j) { + return 0.04 * j - i; + } + }); + + } + + public void testDeterminant() { + + GeneralSquareMatrix m1 = buildProblem1().a; + assertTrue(Math.abs(m1.getDeterminant(1.0e-10) - 6.0) < 1.0e-10); + + GeneralSquareMatrix m2 = buildProblem2().a; + assertTrue(Math.abs(m2.getDeterminant(1.0e-10) + 0.9999999) < 1.0e-10); + + GeneralSquareMatrix m3 = buildProblem3().a; + assertTrue(Math.abs(m3.getDeterminant(1.0e-10) - 0.0) < 1.0e-10); + + } + + public void testSolve() + throws SingularMatrixException { + + LinearProblem p; + Matrix result; + + p = buildProblem1(); + result = p.a.solve(p.b, 1.0e-10); + checkSolve(p, result); + + p = buildProblem2(); + result = p.a.solve(p.b, 1.0e-10); + checkSolve(p, result); + + boolean gotIt = false; + try { + p = buildProblem3(); + result = p.a.solve(p.b, 1.0e-10); + } catch(SingularMatrixException e) { + gotIt = true; + } + assertTrue(gotIt); + + } + + public void testInverse() + throws SingularMatrixException { + + SquareMatrix a, inverse; + + a = buildProblem1().a; + inverse = a.getInverse(1.0e-10); + checkMatrix(a.mul(inverse), new ElementPattern() { + public double value(int i, int j) { + return (i == j) ? 1.0 : 0.0; + } + }); + + a = buildProblem2().a; + inverse = a.getInverse(1.0e-10); + checkMatrix(a.mul(inverse), new ElementPattern() { + public double value(int i, int j) { + return (i == j) ? 1.0 : 0.0; + } + }); + + boolean gotIt = false; + try { + a = buildProblem3().a; + inverse = a.getInverse(1.0e-10); + } catch(SingularMatrixException e) { + gotIt = true; + } + assertTrue(gotIt); + + } + + public static Test suite() { + return new TestSuite(GeneralSquareMatrixTest.class); + } + + public interface ElementPattern { + public double value(int i, int j); + } + + public GeneralSquareMatrix buildMatrix(int order, + ElementPattern pattern) { + GeneralSquareMatrix m = new GeneralSquareMatrix(order); + + for (int i = 0; i < m.getRows(); ++i) { + for (int j = 0; j < m.getColumns(); ++j){ + m.setElement(i, j, pattern.value(i, j)); + } + } + + return m; + + } + + public void checkMatrix(Matrix m, ElementPattern pattern) { + for (int i = 0; i < m.getRows(); ++i) { + for (int j = 0; j < m.getColumns(); ++j) { + assertTrue(Math.abs(m.getElement(i, j) - pattern.value(i, j)) + < 1.0e-10); + } + } + } + + private class LinearProblem { + public GeneralSquareMatrix a; + public Matrix x; + public Matrix b; + public LinearProblem(GeneralSquareMatrix a, Matrix x, Matrix b) { + this.a = a; + this.x = x; + this.b = b; + } + } + + private LinearProblem buildProblem1() { + + GeneralSquareMatrix a = new GeneralSquareMatrix(4); + + a.setElement(0, 0, 2.0); + a.setElement(0, 1, 1.0); + a.setElement(0, 2, 0.0); + a.setElement(0, 3, 4.0); + + a.setElement(1, 0, -4.0); + a.setElement(1, 1, -2.0); + a.setElement(1, 2, 3.0); + a.setElement(1, 3, -7.0); + + a.setElement(2, 0, 4.0); + a.setElement(2, 1, 1.0); + a.setElement(2, 2, -2.0); + a.setElement(2, 3, 8.0); + + a.setElement(3, 0, 0.0); + a.setElement(3, 1, -3.0); + a.setElement(3, 2, -12.0); + a.setElement(3, 3, -1.0); + + GeneralMatrix x = new GeneralMatrix(4, 1); + + x.setElement(0, 0, 3.0); + x.setElement(1, 0, 4.0); + x.setElement(2, 0, -1.0); + x.setElement(3, 0, -2.0); + + GeneralMatrix b = new GeneralMatrix(4, 1); + + b.setElement(0, 0, 2.0); + b.setElement(1, 0, -9.0); + b.setElement(2, 0, 2.0); + b.setElement(3, 0, 2.0); + + return new LinearProblem(a, x, b); + + } + + private LinearProblem buildProblem2() + { + + double epsilon = 1.0e-7; + + GeneralSquareMatrix a = new GeneralSquareMatrix(2); + + a.setElement(0, 0, epsilon); + a.setElement(0, 1, 1.0); + + a.setElement(1, 0, 1.0); + a.setElement(1, 1, 1.0); + + GeneralMatrix x = new GeneralMatrix(2, 2); + + x.setElement(0, 0, 1.0 + epsilon); + x.setElement(1, 0, 1.0 - epsilon); + + x.setElement(0, 1, epsilon); + x.setElement(1, 1, 1.0); + + GeneralMatrix b = new GeneralMatrix(2, 2); + + b.setElement(0, 0, 1.0 + epsilon * epsilon); + b.setElement(1, 0, 2.0); + + b.setElement(0, 1, 1.0 + epsilon * epsilon); + b.setElement(1, 1, 1.0 + epsilon); + + return new LinearProblem(a, x, b); + + } + + private LinearProblem buildProblem3 () + { + + GeneralSquareMatrix a = new GeneralSquareMatrix(3); + + a.setElement(0, 0, 1.0); + a.setElement(0, 1, 2.0); + a.setElement(0, 1, -3.0); + + a.setElement(1, 0, 2.0); + a.setElement(1, 1, 1.0); + a.setElement(1, 1, 3.0); + + a.setElement(2, 0, -3.0); + a.setElement(2, 1, 0.0); + a.setElement(2, 1, -9.0); + + GeneralMatrix x = new GeneralMatrix(3, 1); + GeneralMatrix b = new GeneralMatrix(3, 1); + + return new LinearProblem(a, x, b); + + } + + private void checkSolve(LinearProblem p, Matrix result) + { + + Matrix residual = p.a.mul(result).sub(p.b); + for (int i = 0; i < residual.getRows(); ++i) { + for (int j = 0; j < residual.getColumns(); ++j) { + assertTrue(Math.abs(residual.getElement(i, j)) < 1.0e-10); + } + } + + for (int i = 0; i < result.getRows(); ++i) { + for (int j = 0; j < result.getColumns(); ++j) { + assertTrue(Math.abs(result.getElement(i, j) - p.x.getElement(i, j)) + < 1.0e-10); + } + } + + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/linalg/LowerTriangularMatrixTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/linalg/LowerTriangularMatrixTest.java new file mode 100644 index 000000000..d19a3d6fc --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/linalg/LowerTriangularMatrixTest.java @@ -0,0 +1,261 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.linalg; + +import junit.framework.*; + +public class LowerTriangularMatrixTest + extends TestCase { + + public LowerTriangularMatrixTest(String name) { + super(name); + } + + public void testNoSetOutsideOfLowerTriangle() { + + LowerTriangularMatrix l = new LowerTriangularMatrix(4); + + for (int i = 0; i < l.getRows(); ++i) { + for (int j = 0; j < l.getColumns(); ++j) { + + if (i >= j) { + l.setElement(i, j, i + 0.1 * j); + } else { + boolean gotIt = false; + try { + l.setElement + (i, j, -1.3); + } catch(ArrayIndexOutOfBoundsException e) { + gotIt = true; + } + assertTrue(gotIt); + } + } + } + + checkMatrix(l, new ElementPattern() { + public double value(int i, int j) { + return i + 0.1 * j; + } + }); + + } + + public void testCopy() { + + LowerTriangularMatrix l1 = buildMatrix(4, new ElementPattern() { + public double value(int i, int j) { + return i + 0.1 * j; + } + }); + + LowerTriangularMatrix l2 = new LowerTriangularMatrix (l1); + + checkMatrix (l2, new ElementPattern() { + public double value(int i, int j) { + return i + 0.1 * j; + } + }); + + } + + public void testDuplicate() { + + LowerTriangularMatrix l1 = buildMatrix(4, new ElementPattern() { + public double value(int i, int j) { + return i + 0.1 * j; + } + }); + + Matrix l2 = l1.duplicate(); + assertTrue(l2 instanceof LowerTriangularMatrix); + + checkMatrix(l2, new ElementPattern() { + public double value(int i, int j) { + return i + 0.1 * j; + } + }); + + } + + public void testTranspose() { + + LowerTriangularMatrix l = buildMatrix(7, new ElementPattern() { + public double value(int i, int j) { + return i + 0.1 * j; + } + }); + + Matrix transposed = l.getTranspose(); + assertTrue(transposed instanceof UpperTriangularMatrix); + + for (int i = 0; i < transposed.getRows(); ++i){ + for (int j = 0; j < transposed.getColumns(); ++j) { + double expected = (i > j) ? 0.0 : (j + 0.1 * i); + assertTrue(Math.abs(transposed.getElement(i, j) - expected) < 1.0e-10); + } + } + + } + + public void testSelfAdd() { + LowerTriangularMatrix l1 = buildMatrix(7, new ElementPattern() { + public double value(int i, int j) { + return 3 * i - 0.2 * j; + } + }); + + LowerTriangularMatrix l2 = buildMatrix(7, new ElementPattern() { + public double value(int i, int j) { + return 2 * i - 0.4 * j; } + }); + + l1.selfAdd(l2); + + checkMatrix(l1, new ElementPattern() { + public double value(int i, int j) { + return 5 * i - 0.6 * j; + } + }); + } + + public void testSelfSub() { + LowerTriangularMatrix l1 = buildMatrix(7, new ElementPattern() { + public double value(int i, int j) { + return 3 * i - 0.2 * j; + } + }); + + LowerTriangularMatrix l2 = buildMatrix(7, new ElementPattern() { + public double value(int i, int j) { + return 2 * i - 0.4 * j; + } + }); + + l1.selfSub(l2); + + checkMatrix(l1, new ElementPattern() { + public double value(int i, int j) { + return i + 0.2 * j; + } + }); + } + + public void testDeterminant() { + + LowerTriangularMatrix l = buildMatrix(4, new ElementPattern() { + public double value(int i, int j) { + return (i == j) ? 2.0 : 1.0; + } + }); + + assertTrue(Math.abs(l.getDeterminant(1.0e-10) - Math.pow(2.0, l.getRows())) + < 1.0e-10); + + } + + public void testSolve() + throws SingularMatrixException { + + LowerTriangularMatrix l = buildMatrix(7, new ElementPattern() { + public double value(int i, int j) { + return 1.0; + } + }); + + GeneralMatrix b = new GeneralMatrix(l.getRows(), 3); + for (int i = 0; i < b.getRows(); ++i) { + b.setElement(i, 0, i + 1.0); + b.setElement(i, 1, (i + 1.0) * (i + 2.0) / 2.0); + b.setElement(i, 2, 0.0); + } + + Matrix result = l.solve(b, 1.0e-10); + + assertTrue(result.getRows() == b.getRows()); + assertTrue(result.getColumns() == b.getColumns()); + + for (int i = 0; i < result.getRows(); ++i) { + assertTrue(Math.abs(result.getElement(i, 0) - 1.0) < 1.0e-10); + assertTrue(Math.abs(result.getElement(i, 1) - (i + 1.0)) < 1.0e-10); + assertTrue(Math.abs(result.getElement(i, 2) - 0.0) < 1.0e-10); + } + + boolean gotIt = false; + try { + l.setElement(3, 3, 0.0); + result = l.solve(b, 1.0e-10); + } catch(SingularMatrixException e) { + gotIt = true; + } + assertTrue(gotIt); + + } + + public void testInverse() + throws SingularMatrixException { + + LowerTriangularMatrix l = buildMatrix(5, new ElementPattern() { + public double value(int i, int j) { + return 1.0; + } + }); + + Matrix inverse = l.getInverse(1.0e-10); + assertTrue(inverse instanceof LowerTriangularMatrix); + + checkMatrix(inverse, new ElementPattern() { + public double value(int i, int j) { + return (i == j) ? 1.0 : ((i == j + 1) ? -1.0 : 0.0); + } + }); + + } + + public static Test suite() { + return new TestSuite(LowerTriangularMatrixTest.class); + } + + public interface ElementPattern { + public double value(int i, int j); + } + + public LowerTriangularMatrix buildMatrix(int order, + ElementPattern pattern) { + LowerTriangularMatrix m = new LowerTriangularMatrix(order); + + for (int i = 0; i < m.getRows(); ++i) { + for (int j = 0; j <= i; ++j) { + m.setElement(i, j, pattern.value(i, j)); + } + } + + return m; + + } + + public void checkMatrix(Matrix m, ElementPattern pattern) { + for (int i = 0; i < m.getRows(); ++i) { + for (int j = 0; j < m.getColumns(); ++j) { + double expected = (j <= i) ? pattern.value(i, j) : 0.0; + assertTrue(Math.abs(m.getElement(i, j) - expected) < 1.0e-10); + } + } + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/linalg/MatrixFactoryTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/linalg/MatrixFactoryTest.java new file mode 100644 index 000000000..f491fb35f --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/linalg/MatrixFactoryTest.java @@ -0,0 +1,71 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.linalg; + +import junit.framework.*; + +public class MatrixFactoryTest + extends TestCase { + + public MatrixFactoryTest(String name) { + super(name); + } + + public void testInvalidDimensions() { + boolean gotIt; + + gotIt = false; + try { + MatrixFactory.buildMatrix(0, 2, null, 1, 1); + } catch (IllegalArgumentException e) { + gotIt = true; + } + assertTrue(gotIt); + + } + + public void testDiagonal() { + Matrix m = MatrixFactory.buildMatrix(3, 3, null, 0, 0); + assertTrue(m instanceof DiagonalMatrix); + } + + public void testLowerTriangular() { + Matrix m = MatrixFactory.buildMatrix(3, 3, null, 1, 0); + assertTrue(m instanceof LowerTriangularMatrix); + } + + public void testUpperTriangular() { + Matrix m = MatrixFactory.buildMatrix(3, 3, null, 0, 1); + assertTrue(m instanceof UpperTriangularMatrix); + } + + public void testSquare() { + Matrix m = MatrixFactory.buildMatrix(3, 3, null, 1, 1); + assertTrue(m instanceof GeneralSquareMatrix); + } + + public void testGeneral() { + Matrix m = MatrixFactory.buildMatrix(3, 4, null, 0, 0); + assertTrue(m instanceof GeneralMatrix); + } + + public static Test suite() { + return new TestSuite(MatrixFactoryTest.class); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/linalg/NonNullRangeTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/linalg/NonNullRangeTest.java new file mode 100644 index 000000000..edd136e38 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/linalg/NonNullRangeTest.java @@ -0,0 +1,62 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.linalg; + +import junit.framework.*; + +public class NonNullRangeTest + extends TestCase { + + public NonNullRangeTest(String name) { + super(name); + } + + public void testPublicAttributes() { + NonNullRange r = new NonNullRange(2, 7); + assertTrue(r.begin == 2); + assertTrue(r.end == 7); + } + + public void testCopy() { + NonNullRange r1 = new NonNullRange(2, 7); + NonNullRange r2 = new NonNullRange(r1); + assertTrue(r2.begin == r1.begin); + assertTrue(r1.end == r1.end); + } + + public void testIntersection() { + NonNullRange r1 = new NonNullRange(-4, 8); + NonNullRange r2 = new NonNullRange(3, 12); + NonNullRange r3 = NonNullRange.intersection(r1, r2); + assertTrue(r3.begin == 3); + assertTrue(r3.end == 8); + } + + public void testReunion() { + NonNullRange r1 = new NonNullRange(-4, 8); + NonNullRange r2 = new NonNullRange(3, 12); + NonNullRange r3 = NonNullRange.reunion(r1, r2); + assertTrue(r3.begin == -4); + assertTrue(r3.end == 12); + } + + public static Test suite() { + return new TestSuite(NonNullRangeTest.class); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/linalg/SymetricalMatrixTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/linalg/SymetricalMatrixTest.java new file mode 100644 index 000000000..9c306bd17 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/linalg/SymetricalMatrixTest.java @@ -0,0 +1,272 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.linalg; + +import junit.framework.*; + +public class SymetricalMatrixTest + extends TestCase { + + public SymetricalMatrixTest(String name) { + super(name); + } + + public void testBuildWAAt() { + + double[] a = { 1.0, 2.0, 3.0 }; + SymetricalMatrix s = new SymetricalMatrix(0.99, a); + + checkMatrix(s, new ElementPattern() { + public double value(int i, int j) { + return 0.99 * (i + 1) * (j + 1); + } + }); + + } + + public void testNoSetOutsideOfDiagonal() { + + SymetricalMatrix s = new SymetricalMatrix(4); + + for (int i = 0; i < s.getRows(); ++i) { + for (int j = 0; j < s.getColumns(); ++j) { + if (i == j) { + s.setElement(i, j, 0.5); + } else { + boolean gotIt = false; + try { + s.setElement + (i, j, -1.3); + } catch(ArrayIndexOutOfBoundsException e) { + gotIt = true; + } + assertTrue(gotIt); + } + } + } + + checkMatrix(s, new ElementPattern() { + public double value(int i, int j) { + return (i == j) ? 0.5 : 0.0; + } + }); + + } + + public void testSetElementAndSymetricalElement() { + SymetricalMatrix s = new SymetricalMatrix(5); + s.setElementAndSymetricalElement(1, 2, 3.4); + assertTrue(Math.abs(s.getElement(1, 2) - 3.4) < 1.0e-10); + assertTrue(Math.abs(s.getElement(2, 1) - 3.4) < 1.0e-10); + } + + public void testCopy() { + SymetricalMatrix m1 = buildMatrix(5, new ElementPattern() { + public double value(int i, int j) { + return i * i + j * j; + } + }); + + SymetricalMatrix m2 = new SymetricalMatrix(m1); + + for (int i = 0; i < m1.getRows(); ++i) { + for (int j = i; j < m1.getColumns(); ++j) { + m1.setElementAndSymetricalElement(i, j, -1.0); + } + } + + assertTrue(m2.getRows() == m1.getRows()); + assertTrue(m2.getColumns() == m1.getColumns()); + + checkMatrix(m2, new ElementPattern() { + public double value(int i, int j) { + return i * i + j * j; + } + }); + + } + + public void testDuplicate() { + SymetricalMatrix m1 = buildMatrix(5, new ElementPattern() { + public double value(int i, int j) { + return i * j; + } + }); + + Matrix m2 = m1.duplicate(); + assertTrue(m2 instanceof SymetricalMatrix); + + for (int i = 0; i < m1.getRows(); ++i) { + for (int j = i; j < m1.getColumns(); ++j) { + m1.setElementAndSymetricalElement(i, j, -1.0); + } + } + + assertTrue(m2.getRows() == m1.getRows()); + assertTrue(m2.getColumns() == m1.getColumns()); + + checkMatrix(m2, new ElementPattern() { + public double value(int i, int j) { + return i * j; + } + }); + + } + + public void testSelfAdd() { + double[] a1 = { 2.0, 4.0, 8.0, 16.0 }; + SymetricalMatrix s1 = new SymetricalMatrix(0.5, a1); + + double[] a2 = { 3.0, 9.0, 27.0, 81.0 }; + SymetricalMatrix s2 = new SymetricalMatrix(1.0, a2); + + s1.selfAdd(s2); + + checkMatrix(s1, new ElementPattern() { + public double value(int i, int j) { + return 0.5 * Math.pow(2.0, i + 1) * Math.pow(2.0, j + 1) + + Math.pow(3.0, i + 1) * Math.pow(3.0, j + 1); + } + }); + } + + public void testSelfSub() { + double[] a1 = { 2.0, 4.0, 8.0, 16.0 }; + SymetricalMatrix s1 = new SymetricalMatrix(0.5, a1); + + double[] a2 = { 3.0, 9.0, 27.0, 81.0 }; + SymetricalMatrix s2 = new SymetricalMatrix(1.0, a2); + + s1.selfSub(s2); + + checkMatrix(s1, new ElementPattern() { + public double value(int i, int j) { + return 0.5 * Math.pow(2.0, i + 1) * Math.pow(2.0, j + 1) + - Math.pow(3.0, i + 1) * Math.pow(3.0, j + 1); + } + }); + } + + public void testSelfAddWAAt() { + + SymetricalMatrix s = new SymetricalMatrix(3); + + double[] a1 = { 1.0, 2.0, 3.0 }; + s.selfAddWAAt(1.0, a1); + + double[] a2 = { 0.1, 0.2, 0.3 }; + s.selfAddWAAt(2.0, a2); + + checkMatrix(s, new ElementPattern() { + public double value(int i, int j) { + return 1.02 * (i + 1) * (j + 1); + } + }); + + } + + public void testSingular() + throws SingularMatrixException { + SymetricalMatrix s = new SymetricalMatrix(3); + + double[] a1 = { 1.0, 2.0, 3.0 }; + s.selfAddWAAt(1.0, a1); + + double[] a2 = { 0.1, 0.2, 0.3 }; + s.selfAddWAAt(2.0, a2); + + Matrix b = new GeneralMatrix(3, 1); + b.setElement(0, 0, 6.12); + b.setElement(1, 0, 12.24); + b.setElement(2, 0, 18.36); + + boolean gotIt = false; + try { + s.solve(b, 1.0e-10); + } catch(SingularMatrixException e) { + gotIt = true; + } + assertTrue(gotIt); + + } + + public void testSolve() + throws SingularMatrixException { + SymetricalMatrix s = new SymetricalMatrix(3); + + double[] a1 = { 1.0, 2.0, 3.0 }; + s.selfAddWAAt(1.0, a1); + + double[] a2 = { 0.1, 0.2, 0.3 }; + s.selfAddWAAt(2.0, a2); + + double[] a3 = { 1.2, -3.0, 2.1 }; + s.selfAddWAAt(3.0, a3); + + double[] a4 = { 0.4, 0.1, 3.1 }; + s.selfAddWAAt(2.0, a4); + + Matrix b = new GeneralMatrix(3, 1); + b.setElement(0, 0, 10.08); + b.setElement(1, 0, 10.26); + b.setElement(2, 0, 42.57); + + Matrix x = s.solve(b, 1.0e-10); + + checkMatrix (x, new ElementPattern() { + public double value(int i, int j) { + return 1.0; + } + }); + + assertTrue(Math.abs(s.getDeterminant(1.0e-10) - 782.846532) < 1.0e-10); + + } + + public static Test suite() { + return new TestSuite(SymetricalMatrixTest.class); + } + + public interface ElementPattern { + public double value(int i, int j); + } + + public SymetricalMatrix buildMatrix(int order, + ElementPattern pattern) { + SymetricalMatrix m = new SymetricalMatrix(order); + + for (int i = 0; i < m.getRows(); ++i) { + for (int j = i; j < m.getColumns(); ++j) { + m.setElementAndSymetricalElement(i, j, pattern.value(i, j)); + } + } + + return m; + + } + + public void checkMatrix(Matrix m, ElementPattern pattern) { + for (int i = 0; i < m.getRows(); ++i) { + for (int j = 0; j < m.getColumns(); ++j) { + assertTrue(Math.abs(m.getElement(i, j) - pattern.value(i, j)) + < 1.0e-10); + } + } + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/linalg/UpperTriangularMatrixTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/linalg/UpperTriangularMatrixTest.java new file mode 100644 index 000000000..84c81ce2b --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/linalg/UpperTriangularMatrixTest.java @@ -0,0 +1,261 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.linalg; + +import junit.framework.*; + +public class UpperTriangularMatrixTest + extends TestCase { + + public UpperTriangularMatrixTest(String name) { + super(name); + } + + public void testNoSetOutsideOfUpperTriangle() { + + UpperTriangularMatrix u = new UpperTriangularMatrix(4); + + for (int i = 0; i < u.getRows(); ++i) { + for (int j = 0; j < u.getColumns(); ++j) { + if (i <= j) { + u.setElement(i, j, i + 0.1 * j); + } else { + boolean gotIt = false; + try { + u.setElement(i, j, -1.3); + } catch(ArrayIndexOutOfBoundsException e) { + gotIt = true; + } + assertTrue(gotIt); + } + } + } + + checkMatrix(u, new ElementPattern() { + public double value(int i, int j) { + return i + 0.1 * j; + } + }); + + } + + public void testCopy() { + + UpperTriangularMatrix u1 = buildMatrix(4, new ElementPattern() { + public double value(int i, int j) { + return i + 0.1 * j; + } + }); + + UpperTriangularMatrix u2 = new UpperTriangularMatrix(u1); + + checkMatrix(u2, new ElementPattern() { + public double value(int i, int j) { + return i + 0.1 * j; + } + }); + + } + + public void testDuplicate() { + + UpperTriangularMatrix u1 = buildMatrix(4, new ElementPattern() { + public double value(int i, int j) { + return i + 0.1 * j; + } + }); + + Matrix u2 = u1.duplicate(); + assertTrue(u2 instanceof UpperTriangularMatrix); + + checkMatrix(u2, new ElementPattern() { + public double value(int i, int j) { + return i + 0.1 * j; + } + }); + + } + + public void testTranspose() { + + UpperTriangularMatrix u = buildMatrix(7, new ElementPattern() { + public double value(int i, int j) { + return i + 0.1 * j; + } + }); + + Matrix transposed = u.getTranspose(); + assertTrue(transposed instanceof LowerTriangularMatrix); + + for (int i = 0; i < transposed.getRows(); ++i){ + for (int j = 0; j < transposed.getColumns(); ++j) { + double expected = (i < j) ? 0.0 : (j + 0.1 * i); + assertTrue(Math.abs(transposed.getElement(i, j) - expected) < 1.0e-10); + } + } + + } + + public void testSelfAdd() { + UpperTriangularMatrix u1 = buildMatrix(7, new ElementPattern() { + public double value(int i, int j) { + return 3 * i - 0.2 * j; + } + }); + + UpperTriangularMatrix u2 = buildMatrix(7, new ElementPattern() { + public double value(int i, int j) { + return 2 * i - 0.4 * j; + } + }); + + u1.selfAdd(u2); + + checkMatrix(u1, new ElementPattern() { + public double value(int i, int j) { + return 5 * i - 0.6 * j; + } + }); + } + + public void testSelfSub() { + UpperTriangularMatrix u1 = buildMatrix(7, new ElementPattern() { + public double value(int i, int j) { + return 3 * i - 0.2 * j; + } + }); + + UpperTriangularMatrix u2 = buildMatrix(7, new ElementPattern() { + public double value(int i, int j) { + return 2 * i - 0.4 * j; + } + }); + + u1.selfSub(u2); + + checkMatrix(u1, new ElementPattern() { + public double value(int i, int j) { + return i + 0.2 * j; + } + }); + } + + public void testDeterminant() { + + UpperTriangularMatrix u = buildMatrix(4, new ElementPattern() { + public double value(int i, int j) { + return (i == j) ? 2.0 : 1.0; + } + }); + + assertTrue(Math.abs(u.getDeterminant(1.0e-10) - Math.pow(2.0, u.getRows())) + < 1.0e-10); + + } + + public void testSolve() + throws SingularMatrixException { + + int rows = 7; + UpperTriangularMatrix u = buildMatrix(rows, new ElementPattern() { + public double value(int i, int j) { + return 1.0; + } + }); + + GeneralMatrix b = new GeneralMatrix(rows, 3); + for (int i = 0; i < rows; ++i) { + b.setElement(i, 0, rows - i); + b.setElement(i, 1, (rows - i) * (rows + 1 - i) / 2.0); + b.setElement(i, 2, 0.0); + } + + Matrix result = u.solve(b, 1.0e-10); + + assertTrue(result.getRows() == b.getRows()); + assertTrue(result.getColumns() == b.getColumns()); + + for (int i = 0; i < result.getRows(); ++i) { + assertTrue(Math.abs(result.getElement(i, 0) - 1.0) < 1.0e-10); + assertTrue(Math.abs(result.getElement(i, 1) - (rows - i)) < 1.0e-10); + assertTrue(Math.abs(result.getElement(i, 2) - 0.0) < 1.0e-10); + } + + boolean gotIt = false; + try { + u.setElement(3, 3, 0.0); + result = u.solve(b, 1.0e-10); + } catch(SingularMatrixException e) { + gotIt = true; + } + assertTrue(gotIt); + + } + + public void testInverse() + throws SingularMatrixException { + + UpperTriangularMatrix u = buildMatrix(5, new ElementPattern() { + public double value(int i, int j) { + return 1.0; + } + }); + + Matrix inverse = u.getInverse(1.0e-10); + assertTrue(inverse instanceof UpperTriangularMatrix); + + checkMatrix(inverse, new ElementPattern() { + public double value(int i, int j) { + return (i == j) ? 1.0 : ((i == j - 1) ? -1.0 : 0.0); + } + }); + + } + + public static Test suite() { + return new TestSuite(UpperTriangularMatrixTest.class); + } + + public interface ElementPattern { + public double value(int i, int j); + } + + public UpperTriangularMatrix buildMatrix(int order, + ElementPattern pattern) { + UpperTriangularMatrix m = new UpperTriangularMatrix (order); + + for (int i = 0; i < m.getRows(); ++i) { + for (int j = i; j < m.getColumns(); ++j) { + m.setElement(i, j, pattern.value(i, j)); + } + } + + return m; + + } + + public void checkMatrix(Matrix m, ElementPattern pattern) { + for (int i = 0; i < m.getRows(); ++i) { + for (int j = 0; j < m.getColumns(); ++j) { + double expected = (i <= j) ? pattern.value(i, j) : 0.0; + assertTrue(Math.abs(m.getElement(i, j) - expected) < 1.0e-10); + } + } + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/ode/AllTests.java b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/AllTests.java new file mode 100644 index 000000000..076f41ef6 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/AllTests.java @@ -0,0 +1,53 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class AllTests { + public static Test suite() { + + TestSuite suite = new TestSuite("org.spaceroots.mantissa.ode"); + + suite.addTest(EulerStepInterpolatorTest.suite()); + suite.addTest(EulerIntegratorTest.suite()); + suite.addTest(MidpointIntegratorTest.suite()); + suite.addTest(ClassicalRungeKuttaIntegratorTest.suite()); + suite.addTest(GillIntegratorTest.suite()); + suite.addTest(ThreeEighthesIntegratorTest.suite()); + suite.addTest(HighamHall54IntegratorTest.suite()); + suite.addTest(DormandPrince54IntegratorTest.suite()); + suite.addTest(DormandPrince853IntegratorTest.suite()); + suite.addTest(GraggBulirschStoerIntegratorTest.suite()); + suite.addTest(FirstOrderConverterTest.suite()); + suite.addTest(StepNormalizerTest.suite()); + suite.addTest(ContinuousOutputModelTest.suite()); + suite.addTest(ClassicalRungeKuttaStepInterpolatorTest.suite()); + suite.addTest(GillStepInterpolatorTest.suite()); + suite.addTest(ThreeEighthesStepInterpolatorTest.suite()); + suite.addTest(DormandPrince853StepInterpolatorTest.suite()); + suite.addTest(DormandPrince54StepInterpolatorTest.suite()); + suite.addTest(HighamHall54StepInterpolatorTest.suite()); + suite.addTest(MidpointStepInterpolatorTest.suite()); + suite.addTest(GraggBulirschStoerStepInterpolatorTest.suite()); + + return suite; + + } +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/ode/ClassicalRungeKuttaIntegratorTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/ClassicalRungeKuttaIntegratorTest.java new file mode 100644 index 000000000..a4681f5c2 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/ClassicalRungeKuttaIntegratorTest.java @@ -0,0 +1,216 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +import junit.framework.*; + +import org.spaceroots.mantissa.estimation.EstimationException; +import org.spaceroots.mantissa.fitting.PolynomialFitter; + +public class ClassicalRungeKuttaIntegratorTest + extends TestCase { + + public ClassicalRungeKuttaIntegratorTest(String name) { + super(name); + } + + public void testDimensionCheck() { + try { + TestProblem1 pb = new TestProblem1(); + new ClassicalRungeKuttaIntegrator(0.01).integrate(pb, + 0.0, new double[pb.getDimension()+10], + 1.0, new double[pb.getDimension()+10]); + fail("an exception should have been thrown"); + } catch(DerivativeException de) { + fail("wrong exception caught"); + } catch(IntegratorException ie) { + } + } + + public void testNullIntervalCheck() { + try { + TestProblem1 pb = new TestProblem1(); + new ClassicalRungeKuttaIntegrator(0.01).integrate(pb, + 0.0, new double[pb.getDimension()], + 0.0, new double[pb.getDimension()]); + fail("an exception should have been thrown"); + } catch(DerivativeException de) { + fail("wrong exception caught"); + } catch(IntegratorException ie) { + } + } + + public void testDecreasingSteps() + throws DerivativeException, IntegratorException { + + TestProblemAbstract[] problems = TestProblemFactory.getProblems(); + for (int k = 0; k < problems.length; ++k) { + + double previousError = Double.NaN; + for (int i = 4; i < 10; ++i) { + + TestProblemAbstract pb = (TestProblemAbstract) problems[k].clone(); + double step = (pb.getFinalTime() - pb.getInitialTime()) + * Math.pow(2.0, -i); + + FirstOrderIntegrator integ = new ClassicalRungeKuttaIntegrator(step); + TestProblemHandler handler = new TestProblemHandler(pb); + integ.setStepHandler(handler); + SwitchingFunction[] functions = pb.getSwitchingFunctions(); + if (functions != null) { + for (int l = 0; l < functions.length; ++l) { + integ.addSwitchingFunction(functions[l], + Double.POSITIVE_INFINITY, 1.0e-6 * step); + } + } + integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + double error = handler.getMaximalError(); + if (i > 4) { + assertTrue(error < Math.abs(previousError)); + } + previousError = error; + } + + } + + } + + public void testOrder() + throws EstimationException, DerivativeException, + IntegratorException { + PolynomialFitter fitter = new PolynomialFitter(1, + 10, 1.0e-7, 1.0e-10, + 1.0e-10); + + TestProblemAbstract[] problems = TestProblemFactory.getProblems(); + for (int k = 0; k < problems.length; ++k) { + + for (int i = 0; i < 10; ++i) { + + TestProblemAbstract pb = (TestProblemAbstract) problems[k].clone(); + double step = (pb.getFinalTime() - pb.getInitialTime()) + * Math.pow(2.0, -(i + 1)); + + FirstOrderIntegrator integ = new ClassicalRungeKuttaIntegrator(step); + TestProblemHandler handler = new TestProblemHandler(pb); + integ.setStepHandler(handler); + SwitchingFunction[] functions = pb.getSwitchingFunctions(); + if (functions != null) { + for (int l = 0; l < functions.length; ++l) { + integ.addSwitchingFunction(functions[l], + Double.POSITIVE_INFINITY, 1.0e-6 * step); + } + } + integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + fitter.addWeightedPair(1.0, + Math.log(Math.abs(step)), + Math.log(handler.getLastError())); + + } + + // this is an order 4 method + double[] coeffs = fitter.fit(); + assertTrue(coeffs[1] > 3.2); + assertTrue(coeffs[1] < 4.8); + + } + + } + + public void testSmallStep() + throws DerivativeException, IntegratorException { + + TestProblem1 pb = new TestProblem1(); + double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.001; + + FirstOrderIntegrator integ = new ClassicalRungeKuttaIntegrator(step); + TestProblemHandler handler = new TestProblemHandler(pb); + integ.setStepHandler(handler); + integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + assertTrue(handler.getLastError() < 2.0e-13); + assertTrue(handler.getMaximalError() < 4.0e-12); + + } + + public void testBigStep() + throws DerivativeException, IntegratorException { + + TestProblem1 pb = new TestProblem1(); + double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.2; + + FirstOrderIntegrator integ = new ClassicalRungeKuttaIntegrator(step); + TestProblemHandler handler = new TestProblemHandler(pb); + integ.setStepHandler(handler); + integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + assertTrue(handler.getLastError() > 0.0004); + assertTrue(handler.getMaximalError() > 0.005); + + } + + public void testKepler() + throws DerivativeException, IntegratorException { + + final TestProblem3 pb = new TestProblem3(0.9); + double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.0003; + + FirstOrderIntegrator integ = new ClassicalRungeKuttaIntegrator(step); + integ.setStepHandler(new StepHandler() { + private double maxError = 0; + public boolean requiresDenseOutput() { + return false; + } + public void reset() { + maxError = 0; + } + public void handleStep(StepInterpolator interpolator, + boolean isLast) { + + double[] interpolatedY = interpolator.getInterpolatedState (); + double[] theoreticalY = pb.computeTheoreticalState(interpolator.getCurrentTime()); + double dx = interpolatedY[0] - theoreticalY[0]; + double dy = interpolatedY[1] - theoreticalY[1]; + double error = dx * dx + dy * dy; + if (error > maxError) { + maxError = error; + } + if (isLast) { + // even with more than 1000 evaluations per period, + // RK4 is not able to integrate such an eccentric + // orbit with a good accuracy + assertTrue(maxError > 0.005); + } + } + }); + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + } + + public static Test suite() { + return new TestSuite(ClassicalRungeKuttaIntegratorTest.class); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/ode/ClassicalRungeKuttaStepInterpolatorTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/ClassicalRungeKuttaStepInterpolatorTest.java new file mode 100644 index 000000000..0306a083c --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/ClassicalRungeKuttaStepInterpolatorTest.java @@ -0,0 +1,82 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +import junit.framework.*; +import java.util.Random; +import java.io.ByteArrayOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectInputStream; +import java.io.IOException; + +public class ClassicalRungeKuttaStepInterpolatorTest + extends TestCase { + + public ClassicalRungeKuttaStepInterpolatorTest(String name) { + super(name); + } + + public void testSerialization() + throws DerivativeException, IntegratorException, + IOException, ClassNotFoundException { + + TestProblem3 pb = new TestProblem3(0.9); + double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.0003; + ClassicalRungeKuttaIntegrator integ = new ClassicalRungeKuttaIntegrator(step); + integ.setStepHandler(new ContinuousOutputModel()); + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos); + oos.writeObject(integ.getStepHandler()); + + assertTrue(bos.size () > 700000); + assertTrue(bos.size () < 701000); + + ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); + ObjectInputStream ois = new ObjectInputStream(bis); + ContinuousOutputModel cm = (ContinuousOutputModel) ois.readObject(); + + Random random = new Random(347588535632l); + double maxError = 0.0; + for (int i = 0; i < 1000; ++i) { + double r = random.nextDouble(); + double time = r * pb.getInitialTime() + (1.0 - r) * pb.getFinalTime(); + cm.setInterpolatedTime(time); + double[] interpolatedY = cm.getInterpolatedState (); + double[] theoreticalY = pb.computeTheoreticalState(time); + double dx = interpolatedY[0] - theoreticalY[0]; + double dy = interpolatedY[1] - theoreticalY[1]; + double error = dx * dx + dy * dy; + if (error > maxError) { + maxError = error; + } + } + + assertTrue(maxError > 0.005); + + } + + public static Test suite() { + return new TestSuite(ClassicalRungeKuttaStepInterpolatorTest.class); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/ode/ContinuousOutputModelTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/ContinuousOutputModelTest.java new file mode 100644 index 000000000..2af17284d --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/ContinuousOutputModelTest.java @@ -0,0 +1,94 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +import junit.framework.*; +import java.util.Random; + +public class ContinuousOutputModelTest + extends TestCase { + + public ContinuousOutputModelTest(String name) { + super(name); + } + + public void testBoundaries() + throws DerivativeException, IntegratorException { + integ.setStepHandler(new ContinuousOutputModel()); + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + ContinuousOutputModel cm = (ContinuousOutputModel) integ.getStepHandler(); + cm.setInterpolatedTime(2.0 * pb.getInitialTime() - pb.getFinalTime()); + cm.setInterpolatedTime(2.0 * pb.getFinalTime() - pb.getInitialTime()); + cm.setInterpolatedTime(0.5 * (pb.getFinalTime() + pb.getInitialTime())); + } + + public void testRandomAccess() + throws DerivativeException, IntegratorException { + + ContinuousOutputModel cm = new ContinuousOutputModel(); + integ.setStepHandler(cm); + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + Random random = new Random(347588535632l); + double maxError = 0.0; + for (int i = 0; i < 1000; ++i) { + double r = random.nextDouble(); + double time = r * pb.getInitialTime() + (1.0 - r) * pb.getFinalTime(); + cm.setInterpolatedTime(time); + double[] interpolatedY = cm.getInterpolatedState (); + double[] theoreticalY = pb.computeTheoreticalState(time); + double dx = interpolatedY[0] - theoreticalY[0]; + double dy = interpolatedY[1] - theoreticalY[1]; + double error = dx * dx + dy * dy; + if (error > maxError) { + maxError = error; + } + } + + assertTrue(maxError < 1.0e-9); + + } + + public void checkValue(double value, double reference) { + assertTrue(Math.abs(value - reference) < 1.0e-10); + } + + public static Test suite() { + return new TestSuite(ContinuousOutputModelTest.class); + } + + public void setUp() { + pb = new TestProblem3(0.9); + double minStep = 0; + double maxStep = pb.getFinalTime() - pb.getInitialTime(); + integ = new DormandPrince54Integrator(minStep, maxStep, 1.0e-8, 1.0e-8); + } + + public void tearDown() { + pb = null; + integ = null; + } + + TestProblem3 pb; + FirstOrderIntegrator integ; + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/ode/DormandPrince54IntegratorTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/DormandPrince54IntegratorTest.java new file mode 100644 index 000000000..20db1fbee --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/DormandPrince54IntegratorTest.java @@ -0,0 +1,302 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +import junit.framework.*; + +public class DormandPrince54IntegratorTest + extends TestCase { + + public DormandPrince54IntegratorTest(String name) { + super(name); + } + + public void testDimensionCheck() { + try { + TestProblem1 pb = new TestProblem1(); + DormandPrince54Integrator integrator = new DormandPrince54Integrator(0.0, 1.0, + 1.0e-10, 1.0e-10); + integrator.integrate(pb, + 0.0, new double[pb.getDimension()+10], + 1.0, new double[pb.getDimension()+10]); + fail("an exception should have been thrown"); + } catch(DerivativeException de) { + fail("wrong exception caught"); + } catch(IntegratorException ie) { + } + } + + public void testMinStep() + throws DerivativeException, IntegratorException { + + try { + TestProblem1 pb = new TestProblem1(); + double minStep = 0.1 * (pb.getFinalTime() - pb.getInitialTime()); + double maxStep = pb.getFinalTime() - pb.getInitialTime(); + double scalAbsoluteTolerance = 1.0e-15; + double scalRelativeTolerance = 1.0e-15; + + FirstOrderIntegrator integ = new DormandPrince54Integrator(minStep, maxStep, + scalAbsoluteTolerance, + scalRelativeTolerance); + TestProblemHandler handler = new TestProblemHandler(pb); + integ.setStepHandler(handler); + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + fail("an exception should have been thrown"); + } catch(DerivativeException de) { + fail("wrong exception caught"); + } catch(IntegratorException ie) { + } + + } + + public void testSmallLastStep() + throws DerivativeException, IntegratorException { + + TestProblemAbstract pb = new TestProblem5(); + double minStep = 1.25; + double maxStep = Math.abs(pb.getFinalTime() - pb.getInitialTime()); + double scalAbsoluteTolerance = 6.0e-4; + double scalRelativeTolerance = 6.0e-4; + + AdaptiveStepsizeIntegrator integ = + new DormandPrince54Integrator(minStep, maxStep, + scalAbsoluteTolerance, + scalRelativeTolerance); + + DP54SmallLastHandler handler = new DP54SmallLastHandler(minStep); + integ.setStepHandler(handler); + integ.setInitialStepSize(1.7); + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + assertTrue(handler.wasLastSeen()); + + } + + private class DP54SmallLastHandler implements StepHandler { + + public DP54SmallLastHandler(double minStep) { + lastSeen = false; + this.minStep = minStep; + } + + public boolean requiresDenseOutput() { + return false; + } + + public void reset() { + } + + public void handleStep(StepInterpolator interpolator, boolean isLast) { + if (isLast) { + lastSeen = true; + double h = interpolator.getCurrentTime() - interpolator.getPreviousTime(); + assertTrue(Math.abs(h) < minStep); + } + } + + public boolean wasLastSeen() { + return lastSeen; + } + + private boolean lastSeen; + private double minStep; + + } + + public void testIncreasingTolerance() + throws DerivativeException, IntegratorException { + + int previousCalls = Integer.MAX_VALUE; + for (int i = -12; i < -2; ++i) { + TestProblem1 pb = new TestProblem1(); + double minStep = 0; + double maxStep = pb.getFinalTime() - pb.getInitialTime(); + double scalAbsoluteTolerance = Math.pow(10.0, i); + double scalRelativeTolerance = 0.01 * scalAbsoluteTolerance; + + FirstOrderIntegrator integ = new DormandPrince54Integrator(minStep, maxStep, + scalAbsoluteTolerance, + scalRelativeTolerance); + TestProblemHandler handler = new TestProblemHandler(pb); + integ.setStepHandler(handler); + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + // the 0.7 factor is only valid for this test + // and has been obtained from trial and error + // there is no general relation between local and global errors + assertTrue(handler.getMaximalError() < (0.7 * scalAbsoluteTolerance)); + + int calls = pb.getCalls(); + assertTrue(calls <= previousCalls); + previousCalls = calls; + + } + + } + + public void testSwitchingFunctions() + throws DerivativeException, IntegratorException { + + TestProblem4 pb = new TestProblem4(); + double minStep = 0; + double maxStep = pb.getFinalTime() - pb.getInitialTime(); + double scalAbsoluteTolerance = 1.0e-8; + double scalRelativeTolerance = 0.01 * scalAbsoluteTolerance; + + FirstOrderIntegrator integ = new DormandPrince54Integrator(minStep, maxStep, + scalAbsoluteTolerance, + scalRelativeTolerance); + TestProblemHandler handler = new TestProblemHandler(pb); + integ.setStepHandler(handler); + SwitchingFunction[] functions = pb.getSwitchingFunctions(); + if (functions != null) { + for (int l = 0; l < functions.length; ++l) { + integ.addSwitchingFunction(functions[l], + Double.POSITIVE_INFINITY, 1.0e-8 * maxStep); + } + } + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + assertTrue(handler.getMaximalError() < 5.0e-6); + assertEquals(12.0, handler.getLastTime(), 1.0e-8 * maxStep); + + } + + public void testKepler() + throws DerivativeException, IntegratorException { + + final TestProblem3 pb = new TestProblem3(0.9); + double minStep = 0; + double maxStep = pb.getFinalTime() - pb.getInitialTime(); + double scalAbsoluteTolerance = 1.0e-8; + double scalRelativeTolerance = scalAbsoluteTolerance; + + FirstOrderIntegrator integ = new DormandPrince54Integrator(minStep, maxStep, + scalAbsoluteTolerance, + scalRelativeTolerance); + integ.setStepHandler(new StepHandler() { + private int nbSteps = 0; + private double maxError = 0; + public boolean requiresDenseOutput() { + return true; + } + public void reset() { + nbSteps = 0; + maxError = 0; + } + public void handleStep(StepInterpolator interpolator, + boolean isLast) + throws DerivativeException { + + ++nbSteps; + for (int a = 1; a < 10; ++a) { + + double prev = interpolator.getPreviousTime(); + double curr = interpolator.getCurrentTime(); + double interp = ((10 - a) * prev + a * curr) / 10; + interpolator.setInterpolatedTime(interp); + + double[] interpolatedY = interpolator.getInterpolatedState (); + double[] theoreticalY = pb.computeTheoreticalState(interpolator.getInterpolatedTime()); + double dx = interpolatedY[0] - theoreticalY[0]; + double dy = interpolatedY[1] - theoreticalY[1]; + double error = dx * dx + dy * dy; + if (error > maxError) { + maxError = error; + } + } + if (isLast) { + assertTrue(maxError < 7.0e-10); + assertTrue(nbSteps < 400); + } + } + }); + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + assertTrue(pb.getCalls() < 2800); + + } + + public void testVariableSteps() + throws DerivativeException, IntegratorException { + + final TestProblem3 pb = new TestProblem3(0.9); + double minStep = 0; + double maxStep = pb.getFinalTime() - pb.getInitialTime(); + double scalAbsoluteTolerance = 1.0e-8; + double scalRelativeTolerance = scalAbsoluteTolerance; + + FirstOrderIntegrator integ = new DormandPrince54Integrator(minStep, maxStep, + scalAbsoluteTolerance, + scalRelativeTolerance); + integ.setStepHandler(new StepHandler() { + private boolean firstTime = true; + private double minStep = 0; + private double maxStep = 0; + public boolean requiresDenseOutput() { + return false; + } + public void reset() { + firstTime = true; + minStep = 0; + maxStep = 0; + } + public void handleStep(StepInterpolator interpolator, + boolean isLast) { + + double step = Math.abs(interpolator.getCurrentTime() + - interpolator.getPreviousTime()); + if (firstTime) { + minStep = Math.abs(step); + maxStep = minStep; + firstTime = false; + } else { + if (step < minStep) { + minStep = step; + } + if (step > maxStep) { + maxStep = step; + } + } + + if (isLast) { + assertTrue(minStep < (1.0 / 450.0)); + assertTrue(maxStep > (1.0 / 4.2)); + } + } + }); + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + } + + public static Test suite() { + return new TestSuite(DormandPrince54IntegratorTest.class); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/ode/DormandPrince54StepInterpolatorTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/DormandPrince54StepInterpolatorTest.java new file mode 100644 index 000000000..7859aeb46 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/DormandPrince54StepInterpolatorTest.java @@ -0,0 +1,87 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +import junit.framework.*; +import java.util.Random; +import java.io.ByteArrayOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectInputStream; +import java.io.IOException; + +public class DormandPrince54StepInterpolatorTest + extends TestCase { + + public DormandPrince54StepInterpolatorTest(String name) { + super(name); + } + + public void testSerialization() + throws DerivativeException, IntegratorException, + IOException, ClassNotFoundException { + + TestProblem3 pb = new TestProblem3(0.9); + double minStep = 0; + double maxStep = pb.getFinalTime() - pb.getInitialTime(); + double scalAbsoluteTolerance = 1.0e-8; + double scalRelativeTolerance = scalAbsoluteTolerance; + DormandPrince54Integrator integ = new DormandPrince54Integrator(minStep, maxStep, + scalAbsoluteTolerance, + scalRelativeTolerance); + integ.setStepHandler(new ContinuousOutputModel()); + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos); + oos.writeObject(integ.getStepHandler()); + + assertTrue(bos.size () > 119500); + assertTrue(bos.size () < 120500); + + ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); + ObjectInputStream ois = new ObjectInputStream(bis); + ContinuousOutputModel cm = (ContinuousOutputModel) ois.readObject(); + + Random random = new Random(347588535632l); + double maxError = 0.0; + for (int i = 0; i < 1000; ++i) { + double r = random.nextDouble(); + double time = r * pb.getInitialTime() + (1.0 - r) * pb.getFinalTime(); + cm.setInterpolatedTime(time); + double[] interpolatedY = cm.getInterpolatedState (); + double[] theoreticalY = pb.computeTheoreticalState(time); + double dx = interpolatedY[0] - theoreticalY[0]; + double dy = interpolatedY[1] - theoreticalY[1]; + double error = dx * dx + dy * dy; + if (error > maxError) { + maxError = error; + } + } + + assertTrue(maxError < 7.0e-10); + + } + + public static Test suite() { + return new TestSuite(DormandPrince54StepInterpolatorTest.class); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/ode/DormandPrince853IntegratorTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/DormandPrince853IntegratorTest.java new file mode 100644 index 000000000..3cefb9a07 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/DormandPrince853IntegratorTest.java @@ -0,0 +1,312 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +import junit.framework.*; + +public class DormandPrince853IntegratorTest + extends TestCase { + + public DormandPrince853IntegratorTest(String name) { + super(name); + } + + public void testDimensionCheck() { + try { + TestProblem1 pb = new TestProblem1(); + DormandPrince853Integrator integrator = new DormandPrince853Integrator(0.0, 1.0, + 1.0e-10, 1.0e-10); + integrator.integrate(pb, + 0.0, new double[pb.getDimension()+10], + 1.0, new double[pb.getDimension()+10]); + fail("an exception should have been thrown"); + } catch(DerivativeException de) { + fail("wrong exception caught"); + } catch(IntegratorException ie) { + } + } + + public void testNullIntervalCheck() { + try { + TestProblem1 pb = new TestProblem1(); + DormandPrince853Integrator integrator = new DormandPrince853Integrator(0.0, 1.0, + 1.0e-10, 1.0e-10); + integrator.integrate(pb, + 0.0, new double[pb.getDimension()], + 0.0, new double[pb.getDimension()]); + fail("an exception should have been thrown"); + } catch(DerivativeException de) { + fail("wrong exception caught"); + } catch(IntegratorException ie) { + } + } + + public void testMinStep() + throws DerivativeException, IntegratorException { + + try { + TestProblem1 pb = new TestProblem1(); + double minStep = 0.1 * (pb.getFinalTime() - pb.getInitialTime()); + double maxStep = pb.getFinalTime() - pb.getInitialTime(); + double scalAbsoluteTolerance = 1.0e-15; + double scalRelativeTolerance = 1.0e-15; + + FirstOrderIntegrator integ = new DormandPrince853Integrator(minStep, maxStep, + scalAbsoluteTolerance, + scalRelativeTolerance); + TestProblemHandler handler = new TestProblemHandler(pb); + integ.setStepHandler(handler); + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + fail("an exception should have been thrown"); + } catch(DerivativeException de) { + fail("wrong exception caught"); + } catch(IntegratorException ie) { + } + + } + + public void testIncreasingTolerance() + throws DerivativeException, IntegratorException { + + int previousCalls = Integer.MAX_VALUE; + for (int i = -12; i < -2; ++i) { + TestProblem1 pb = new TestProblem1(); + double minStep = 0; + double maxStep = pb.getFinalTime() - pb.getInitialTime(); + double scalAbsoluteTolerance = Math.pow(10.0, i); + double scalRelativeTolerance = 0.01 * scalAbsoluteTolerance; + + FirstOrderIntegrator integ = new DormandPrince853Integrator(minStep, maxStep, + scalAbsoluteTolerance, + scalRelativeTolerance); + TestProblemHandler handler = new TestProblemHandler(pb); + integ.setStepHandler(handler); + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + // the 1.3 factor is only valid for this test + // and has been obtained from trial and error + // there is no general relation between local and global errors + assertTrue(handler.getMaximalError() < (1.3 * scalAbsoluteTolerance)); + + int calls = pb.getCalls(); + assertTrue(calls <= previousCalls); + previousCalls = calls; + + } + + } + + public void testSwitchingFunctions() + throws DerivativeException, IntegratorException { + + TestProblem4 pb = new TestProblem4(); + double minStep = 0; + double maxStep = pb.getFinalTime() - pb.getInitialTime(); + double scalAbsoluteTolerance = 1.0e-9; + double scalRelativeTolerance = 0.01 * scalAbsoluteTolerance; + + FirstOrderIntegrator integ = new DormandPrince853Integrator(minStep, maxStep, + scalAbsoluteTolerance, + scalRelativeTolerance); + TestProblemHandler handler = new TestProblemHandler(pb); + integ.setStepHandler(handler); + SwitchingFunction[] functions = pb.getSwitchingFunctions(); + if (functions != null) { + for (int l = 0; l < functions.length; ++l) { + integ.addSwitchingFunction(functions[l], + Double.POSITIVE_INFINITY, 1.0e-8 * maxStep); + } + } + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + assertTrue(handler.getMaximalError() < 5.0e-8); + assertEquals(12.0, handler.getLastTime(), 1.0e-8 * maxStep); + + } + + public void testKepler() + throws DerivativeException, IntegratorException { + + final TestProblem3 pb = new TestProblem3(0.9); + double minStep = 0; + double maxStep = pb.getFinalTime() - pb.getInitialTime(); + double scalAbsoluteTolerance = 1.0e-8; + double scalRelativeTolerance = scalAbsoluteTolerance; + + FirstOrderIntegrator integ = new DormandPrince853Integrator(minStep, maxStep, + scalAbsoluteTolerance, + scalRelativeTolerance); + integ.setStepHandler(new StepHandler() { + private int nbSteps = 0; + private double maxError = 0; + public boolean requiresDenseOutput() { + return true; + } + public void reset() { + nbSteps = 0; + maxError = 0; + } + public void handleStep(StepInterpolator interpolator, + boolean isLast) + throws DerivativeException { + + ++nbSteps; + for (int a = 1; a < 10; ++a) { + + double prev = interpolator.getPreviousTime(); + double curr = interpolator.getCurrentTime(); + double interp = ((10 - a) * prev + a * curr) / 10; + interpolator.setInterpolatedTime(interp); + + double[] interpolatedY = interpolator.getInterpolatedState (); + double[] theoreticalY = pb.computeTheoreticalState(interpolator.getInterpolatedTime()); + double dx = interpolatedY[0] - theoreticalY[0]; + double dy = interpolatedY[1] - theoreticalY[1]; + double error = dx * dx + dy * dy; + if (error > maxError) { + maxError = error; + } + } + if (isLast) { + assertTrue(maxError < 2.4e-10); + assertTrue(nbSteps < 150); + } + } + }); + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + assertTrue(pb.getCalls() < 2900); + + } + + public void testVariableSteps() + throws DerivativeException, IntegratorException { + + final TestProblem3 pb = new TestProblem3(0.9); + double minStep = 0; + double maxStep = pb.getFinalTime() - pb.getInitialTime(); + double scalAbsoluteTolerance = 1.0e-8; + double scalRelativeTolerance = scalAbsoluteTolerance; + + FirstOrderIntegrator integ = new DormandPrince853Integrator(minStep, maxStep, + scalAbsoluteTolerance, + scalRelativeTolerance); + integ.setStepHandler(new StepHandler() { + private boolean firstTime = true; + private double minStep = 0; + private double maxStep = 0; + public boolean requiresDenseOutput() { + return false; + } + public void reset() { + firstTime = true; + minStep = 0; + maxStep = 0; + } + public void handleStep(StepInterpolator interpolator, + boolean isLast) { + + double step = Math.abs(interpolator.getCurrentTime() + - interpolator.getPreviousTime()); + if (firstTime) { + minStep = Math.abs(step); + maxStep = minStep; + firstTime = false; + } else { + if (step < minStep) { + minStep = step; + } + if (step > maxStep) { + maxStep = step; + } + } + + if (isLast) { + assertTrue(minStep < (1.0 / 100.0)); + assertTrue(maxStep > (1.0 / 2.0)); + } + } + }); + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + } + + public void testNoDenseOutput() + throws DerivativeException, IntegratorException { + TestProblem1 pb1 = new TestProblem1(); + TestProblem1 pb2 = (TestProblem1) pb1.clone(); + double minStep = 0.1 * (pb1.getFinalTime() - pb1.getInitialTime()); + double maxStep = pb1.getFinalTime() - pb1.getInitialTime(); + double scalAbsoluteTolerance = 1.0e-4; + double scalRelativeTolerance = 1.0e-4; + + FirstOrderIntegrator integ = new DormandPrince853Integrator(minStep, maxStep, + scalAbsoluteTolerance, + scalRelativeTolerance); + integ.setStepHandler(new StepHandler() { + public boolean requiresDenseOutput() { + return false; + } + public void reset() { + } + public void handleStep(StepInterpolator interpolator, + boolean isLast) { + } + }); + integ.integrate(pb1, + pb1.getInitialTime(), pb1.getInitialState(), + pb1.getFinalTime(), new double[pb1.getDimension()]); + int callsWithoutDenseOutput = pb1.getCalls(); + + integ.setStepHandler(new StepHandler() { + public boolean requiresDenseOutput() { + return true; + } + public void reset() { + } + public void handleStep(StepInterpolator interpolator, + boolean isLast) + throws DerivativeException { + double prev = interpolator.getPreviousTime(); + double curr = interpolator.getCurrentTime(); + interpolator.setInterpolatedTime(0.5*(prev + curr)); + } + }); + integ.integrate(pb2, + pb2.getInitialTime(), pb2.getInitialState(), + pb2.getFinalTime(), new double[pb2.getDimension()]); + int callsWithDenseOutput = pb2.getCalls(); + + assertTrue(callsWithDenseOutput > callsWithoutDenseOutput); + + } + + public static Test suite() { + return new TestSuite(DormandPrince853IntegratorTest.class); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/ode/DormandPrince853StepInterpolatorTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/DormandPrince853StepInterpolatorTest.java new file mode 100644 index 000000000..7426c2139 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/DormandPrince853StepInterpolatorTest.java @@ -0,0 +1,87 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +import junit.framework.*; +import java.util.Random; +import java.io.ByteArrayOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectInputStream; +import java.io.IOException; + +public class DormandPrince853StepInterpolatorTest + extends TestCase { + + public DormandPrince853StepInterpolatorTest(String name) { + super(name); + } + + public void testSerialization() + throws DerivativeException, IntegratorException, + IOException, ClassNotFoundException { + + TestProblem3 pb = new TestProblem3(0.9); + double minStep = 0; + double maxStep = pb.getFinalTime() - pb.getInitialTime(); + double scalAbsoluteTolerance = 1.0e-8; + double scalRelativeTolerance = scalAbsoluteTolerance; + DormandPrince853Integrator integ = new DormandPrince853Integrator(minStep, maxStep, + scalAbsoluteTolerance, + scalRelativeTolerance); + integ.setStepHandler(new ContinuousOutputModel()); + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos); + oos.writeObject(integ.getStepHandler()); + + assertTrue(bos.size () > 86000); + assertTrue(bos.size () < 87000); + + ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); + ObjectInputStream ois = new ObjectInputStream(bis); + ContinuousOutputModel cm = (ContinuousOutputModel) ois.readObject(); + + Random random = new Random(347588535632l); + double maxError = 0.0; + for (int i = 0; i < 1000; ++i) { + double r = random.nextDouble(); + double time = r * pb.getInitialTime() + (1.0 - r) * pb.getFinalTime(); + cm.setInterpolatedTime(time); + double[] interpolatedY = cm.getInterpolatedState (); + double[] theoreticalY = pb.computeTheoreticalState(time); + double dx = interpolatedY[0] - theoreticalY[0]; + double dy = interpolatedY[1] - theoreticalY[1]; + double error = dx * dx + dy * dy; + if (error > maxError) { + maxError = error; + } + } + + assertTrue(maxError < 2.4e-10); + + } + + public static Test suite() { + return new TestSuite(DormandPrince853StepInterpolatorTest.class); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/ode/EulerIntegratorTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/EulerIntegratorTest.java new file mode 100644 index 000000000..6c75f0096 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/EulerIntegratorTest.java @@ -0,0 +1,169 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +import junit.framework.*; + +import org.spaceroots.mantissa.estimation.EstimationException; +import org.spaceroots.mantissa.fitting.PolynomialFitter; + +public class EulerIntegratorTest + extends TestCase { + + public EulerIntegratorTest(String name) { + super(name); + } + + public void testDimensionCheck() { + try { + TestProblem1 pb = new TestProblem1(); + new EulerIntegrator(0.01).integrate(pb, + 0.0, new double[pb.getDimension()+10], + 1.0, new double[pb.getDimension()+10]); + fail("an exception should have been thrown"); + } catch(DerivativeException de) { + fail("wrong exception caught"); + } catch(IntegratorException ie) { + } + } + + public void testDecreasingSteps() + throws DerivativeException, IntegratorException { + + TestProblemAbstract[] problems = TestProblemFactory.getProblems(); + for (int k = 0; k < problems.length; ++k) { + + double previousError = Double.NaN; + for (int i = 4; i < 10; ++i) { + + TestProblemAbstract pb = (TestProblemAbstract) problems[k].clone(); + double step = (pb.getFinalTime() - pb.getInitialTime()) + * Math.pow(2.0, -i); + + FirstOrderIntegrator integ = new EulerIntegrator(step); + TestProblemHandler handler = new TestProblemHandler(pb); + integ.setStepHandler(handler); + SwitchingFunction[] functions = pb.getSwitchingFunctions(); + if (functions != null) { + for (int l = 0; l < functions.length; ++l) { + integ.addSwitchingFunction(functions[l], + Double.POSITIVE_INFINITY, 1.0e-6 * step); + } + } + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + double error = handler.getMaximalError(); + if (i > 4) { + assertTrue(error < Math.abs(previousError)); + } + previousError = error; + + } + + } + + } + + public void testOrder() + throws EstimationException, DerivativeException, + IntegratorException { + PolynomialFitter fitter = new PolynomialFitter(1, + 10, 1.0e-7, 1.0e-10, + 1.0e-10); + + TestProblemAbstract[] problems = TestProblemFactory.getProblems(); + for (int k = 0; k < problems.length; ++k) { + + for (int i = 0; i < 10; ++i) { + + TestProblemAbstract pb = (TestProblemAbstract) problems[k].clone(); + double step = (pb.getFinalTime() - pb.getInitialTime()) + * Math.pow(2.0, -(i + 1)); + + FirstOrderIntegrator integ = new EulerIntegrator(step); + TestProblemHandler handler = new TestProblemHandler(pb); + integ.setStepHandler(handler); + SwitchingFunction[] functions = pb.getSwitchingFunctions(); + if (functions != null) { + for (int l = 0; l < functions.length; ++l) { + integ.addSwitchingFunction(functions[l], + Double.POSITIVE_INFINITY, 1.0e-6 * step); + } + } + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + fitter.addWeightedPair(1.0, + Math.log(Math.abs(step)), + Math.log(handler.getLastError())); + + } + + // this is an order 1 method + double[] coeffs = fitter.fit(); + assertTrue(coeffs[1] > 0.2); + assertTrue(coeffs[1] < 1.8); + + } + + } + + public void testSmallStep() + throws DerivativeException, IntegratorException { + + TestProblem1 pb = new TestProblem1(); + double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.001; + + FirstOrderIntegrator integ = new EulerIntegrator(step); + TestProblemHandler handler = new TestProblemHandler(pb); + integ.setStepHandler(handler); + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + assertTrue(handler.getLastError() < 2.0e-4); + assertTrue(handler.getMaximalError() < 1.0e-3); + + } + + public void testBigStep() + throws DerivativeException, IntegratorException { + + TestProblem1 pb = new TestProblem1(); + double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.2; + + FirstOrderIntegrator integ = new EulerIntegrator(step); + TestProblemHandler handler = new TestProblemHandler(pb); + integ.setStepHandler(handler); + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + assertTrue(handler.getLastError() > 0.01); + assertTrue(handler.getMaximalError() > 0.2); + + } + + public static Test suite() { + return new TestSuite(EulerIntegratorTest.class); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/ode/EulerStepInterpolatorTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/EulerStepInterpolatorTest.java new file mode 100644 index 000000000..51bf3b403 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/EulerStepInterpolatorTest.java @@ -0,0 +1,170 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +import junit.framework.*; +import java.util.Random; +import java.io.ByteArrayOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectInputStream; +import java.io.IOException; + +public class EulerStepInterpolatorTest + extends TestCase { + + public EulerStepInterpolatorTest(String name) { + super(name); + } + + public void testNoReset() { + + double[] y = { 0.0, 1.0, -2.0 }; + double[][] yDot = { { 1.0, 2.0, -2.0 } }; + EulerStepInterpolator interpolator = new EulerStepInterpolator(); + interpolator.reinitialize(new DummyEquations(), y, yDot, true); + interpolator.storeTime(0); + interpolator.shift(); + interpolator.storeTime(1); + + double[] result = interpolator.getInterpolatedState(); + for (int i = 0; i < result.length; ++i) { + assertTrue(Math.abs(result[i] - y[i]) < 1.0e-10); + } + + } + + public void testInterpolationAtBounds() + throws DerivativeException { + + double t0 = 0; + double[] y0 = {0.0, 1.0, -2.0}; + + double[] y = new double[y0.length]; + System.arraycopy(y0, 0, y, 0, y0.length); + double[][] yDot = { new double[y0.length] }; + EulerStepInterpolator interpolator = new EulerStepInterpolator(); + interpolator.reinitialize(new DummyEquations(), y, yDot, true); + interpolator.storeTime(t0); + + double dt = 1.0; + y[0] = 1.0; + y[1] = 3.0; + y[2] = -4.0; + yDot[0][0] = (y[0] - y0[0]) / dt; + yDot[0][1] = (y[1] - y0[1]) / dt; + yDot[0][2] = (y[2] - y0[2]) / dt; + interpolator.shift(); + interpolator.storeTime(t0 + dt); + + interpolator.setInterpolatedTime(interpolator.getPreviousTime()); + double[] result = interpolator.getInterpolatedState(); + for (int i = 0; i < result.length; ++i) { + assertTrue(Math.abs(result[i] - y0[i]) < 1.0e-10); + } + + interpolator.setInterpolatedTime(interpolator.getCurrentTime()); + result = interpolator.getInterpolatedState(); + for (int i = 0; i < result.length; ++i) { + assertTrue(Math.abs(result[i] - y[i]) < 1.0e-10); + } + + } + + public void testInterpolationInside() + throws DerivativeException { + + double[] y = { 1.0, 3.0, -4.0 }; + double[][] yDot = { { 1.0, 2.0, -2.0 } }; + EulerStepInterpolator interpolator = new EulerStepInterpolator(); + interpolator.reinitialize(new DummyEquations(), y, yDot, true); + interpolator.storeTime(0); + interpolator.shift(); + interpolator.storeTime(1); + + interpolator.setInterpolatedTime(0.1); + double[] result = interpolator.getInterpolatedState(); + assertTrue(Math.abs(result[0] - 0.1) < 1.0e-10); + assertTrue(Math.abs(result[1] - 1.2) < 1.0e-10); + assertTrue(Math.abs(result[2] + 2.2) < 1.0e-10); + + interpolator.setInterpolatedTime(0.5); + result = interpolator.getInterpolatedState(); + assertTrue(Math.abs(result[0] - 0.5) < 1.0e-10); + assertTrue(Math.abs(result[1] - 2.0) < 1.0e-10); + assertTrue(Math.abs(result[2] + 3.0) < 1.0e-10); + + } + + public void testSerialization() + throws DerivativeException, IntegratorException, + IOException, ClassNotFoundException { + + TestProblem1 pb = new TestProblem1(); + double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.001; + EulerIntegrator integ = new EulerIntegrator(step); + integ.setStepHandler(new ContinuousOutputModel()); + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos); + oos.writeObject(integ.getStepHandler()); + + assertTrue(bos.size () > 82000); + assertTrue(bos.size () < 83000); + + ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); + ObjectInputStream ois = new ObjectInputStream(bis); + ContinuousOutputModel cm = (ContinuousOutputModel) ois.readObject(); + + Random random = new Random(347588535632l); + double maxError = 0.0; + for (int i = 0; i < 1000; ++i) { + double r = random.nextDouble(); + double time = r * pb.getInitialTime() + (1.0 - r) * pb.getFinalTime(); + cm.setInterpolatedTime(time); + double[] interpolatedY = cm.getInterpolatedState (); + double[] theoreticalY = pb.computeTheoreticalState(time); + double dx = interpolatedY[0] - theoreticalY[0]; + double dy = interpolatedY[1] - theoreticalY[1]; + double error = dx * dx + dy * dy; + if (error > maxError) { + maxError = error; + } + } + + assertTrue(maxError < 0.001); + + } + + private class DummyEquations + implements FirstOrderDifferentialEquations { + public int getDimension() { + return 0; + } + public void computeDerivatives(double t, double[] y, double[] yDot) { + } + } + + public static Test suite() { + return new TestSuite(EulerStepInterpolatorTest.class); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/ode/FirstOrderConverterTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/FirstOrderConverterTest.java new file mode 100644 index 000000000..d9eb37ab2 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/FirstOrderConverterTest.java @@ -0,0 +1,110 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +import junit.framework.*; + +public class FirstOrderConverterTest + extends TestCase { + + public FirstOrderConverterTest(String name) { + super(name); + } + + public void testDoubleDimension() { + for (int i = 1; i < 10; ++i) { + SecondOrderDifferentialEquations eqn2 = new Equations(i, 0.2); + FirstOrderConverter eqn1 = new FirstOrderConverter(eqn2); + assertTrue(eqn1.getDimension() == (2 * eqn2.getDimension())); + } + } + + public void testDecreasingSteps() + throws DerivativeException, IntegratorException { + + double previousError = Double.NaN; + for (int i = 0; i < 10; ++i) { + + double step = Math.pow(2.0, -(i + 1)); + double error = integrateWithSpecifiedStep(4.0, 0.0, 1.0, step) + - Math.sin(4.0); + if (i > 0) { + assertTrue(Math.abs(error) < Math.abs(previousError)); + } + previousError = error; + + } + } + + public void testSmallStep() + throws DerivativeException, IntegratorException { + double error = integrateWithSpecifiedStep(4.0, 0.0, 1.0, 1.0e-4) + - Math.sin(4.0); + assertTrue(Math.abs(error) < 1.0e-10); + } + + public void testBigStep() + throws DerivativeException, IntegratorException { + double error = integrateWithSpecifiedStep(4.0, 0.0, 1.0, 0.5) + - Math.sin(4.0); + assertTrue(Math.abs(error) > 0.1); + } + + public static Test suite() { + return new TestSuite(FirstOrderConverterTest.class); + } + + private class Equations + implements SecondOrderDifferentialEquations { + + private int n; + + private double omega2; + + public Equations(int n, double omega) { + this.n = n; + omega2 = omega * omega; + } + + public int getDimension() { + return n; + } + + public void computeSecondDerivatives(double t, double[] y, double[] yDot, + double[] yDDot) { + for (int i = 0; i < n; ++i) { + yDDot[i] = -omega2 * y[i]; + } + } + + } + + private double integrateWithSpecifiedStep(double omega, + double t0, double t, + double step) + throws DerivativeException, IntegratorException { + double[] y0 = new double[2]; + y0[0] = Math.sin(omega * t0); + y0[1] = omega * Math.cos(omega * t0); + ClassicalRungeKuttaIntegrator i = new ClassicalRungeKuttaIntegrator(step); + double[] y = new double[2]; + i.integrate(new FirstOrderConverter(new Equations(1, omega)), t0, y0, t, y); + return y[0]; + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/ode/GillIntegratorTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/GillIntegratorTest.java new file mode 100644 index 000000000..5aa0272d7 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/GillIntegratorTest.java @@ -0,0 +1,203 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +import junit.framework.*; + +import org.spaceroots.mantissa.estimation.EstimationException; +import org.spaceroots.mantissa.fitting.PolynomialFitter; + +public class GillIntegratorTest + extends TestCase { + + public GillIntegratorTest(String name) { + super(name); + } + + public void testDimensionCheck() { + try { + TestProblem1 pb = new TestProblem1(); + new GillIntegrator(0.01).integrate(pb, + 0.0, new double[pb.getDimension()+10], + 1.0, new double[pb.getDimension()+10]); + fail("an exception should have been thrown"); + } catch(DerivativeException de) { + fail("wrong exception caught"); + } catch(IntegratorException ie) { + } + } + + public void testDecreasingSteps() + throws DerivativeException, IntegratorException { + + TestProblemAbstract[] problems = TestProblemFactory.getProblems(); + for (int k = 0; k < problems.length; ++k) { + + double previousError = Double.NaN; + for (int i = 5; i < 10; ++i) { + + TestProblemAbstract pb = (TestProblemAbstract) problems[k].clone(); + double step = (pb.getFinalTime() - pb.getInitialTime()) + * Math.pow(2.0, -i); + + FirstOrderIntegrator integ = new GillIntegrator(step); + TestProblemHandler handler = new TestProblemHandler(pb); + integ.setStepHandler(handler); + SwitchingFunction[] functions = pb.getSwitchingFunctions(); + if (functions != null) { + for (int l = 0; l < functions.length; ++l) { + integ.addSwitchingFunction(functions[l], + Double.POSITIVE_INFINITY, 1.0e-6 * step); + } + } + integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + double error = handler.getMaximalError(); + if (i > 5) { + assertTrue(error < Math.abs(previousError)); + } + previousError = error; + } + + } + + } + + public void testOrder() + throws EstimationException, DerivativeException, + IntegratorException { + PolynomialFitter fitter = new PolynomialFitter(1, + 10, 1.0e-7, 1.0e-10, + 1.0e-10); + + TestProblemAbstract[] problems = TestProblemFactory.getProblems(); + for (int k = 0; k < problems.length; ++k) { + + for (int i = 0; i < 10; ++i) { + + TestProblemAbstract pb = (TestProblemAbstract) problems[k].clone(); + double step = (pb.getFinalTime() - pb.getInitialTime()) + * Math.pow(2.0, -(i + 1)); + + FirstOrderIntegrator integ = new GillIntegrator(step); + TestProblemHandler handler = new TestProblemHandler(pb); + integ.setStepHandler(handler); + SwitchingFunction[] functions = pb.getSwitchingFunctions(); + if (functions != null) { + for (int l = 0; l < functions.length; ++l) { + integ.addSwitchingFunction(functions[l], + Double.POSITIVE_INFINITY, 1.0e-6 * step); + } + } + integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + fitter.addWeightedPair(1.0, + Math.log(Math.abs(step)), + Math.log(handler.getLastError())); + + } + + // this is an order 4 method + double[] coeffs = fitter.fit(); + assertTrue(coeffs[1] > 3.2); + assertTrue(coeffs[1] < 4.8); + + } + + } + + public void testSmallStep() + throws DerivativeException, IntegratorException { + + TestProblem1 pb = new TestProblem1(); + double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.001; + + FirstOrderIntegrator integ = new GillIntegrator(step); + TestProblemHandler handler = new TestProblemHandler(pb); + integ.setStepHandler(handler); + integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + assertTrue(handler.getLastError() < 2.0e-13); + assertTrue(handler.getMaximalError() < 4.0e-12); + + } + + public void testBigStep() + throws DerivativeException, IntegratorException { + + TestProblem1 pb = new TestProblem1(); + double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.2; + + FirstOrderIntegrator integ = new GillIntegrator(step); + TestProblemHandler handler = new TestProblemHandler(pb); + integ.setStepHandler(handler); + integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + assertTrue(handler.getLastError() > 0.0004); + assertTrue(handler.getMaximalError() > 0.005); + + } + + public void testKepler() + throws DerivativeException, IntegratorException { + + final TestProblem3 pb = new TestProblem3(0.9); + double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.0003; + + FirstOrderIntegrator integ = new GillIntegrator(step); + integ.setStepHandler(new StepHandler() { + private double maxError = 0; + public boolean requiresDenseOutput() { + return false; + } + public void reset() { + maxError = 0; + } + public void handleStep(StepInterpolator interpolator, + boolean isLast) { + + double[] interpolatedY = interpolator.getInterpolatedState (); + double[] theoreticalY = pb.computeTheoreticalState(interpolator.getCurrentTime()); + double dx = interpolatedY[0] - theoreticalY[0]; + double dy = interpolatedY[1] - theoreticalY[1]; + double error = dx * dx + dy * dy; + if (error > maxError) { + maxError = error; + } + if (isLast) { + // even with more than 1000 evaluations per period, + // RK4 is not able to integrate such an eccentric + // orbit with a good accuracy + assertTrue(maxError > 0.001); + } + } + }); + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + } + + public static Test suite() { + return new TestSuite(GillIntegratorTest.class); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/ode/GillStepInterpolatorTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/GillStepInterpolatorTest.java new file mode 100644 index 000000000..df2c4310e --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/GillStepInterpolatorTest.java @@ -0,0 +1,82 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +import junit.framework.*; +import java.util.Random; +import java.io.ByteArrayOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectInputStream; +import java.io.IOException; + +public class GillStepInterpolatorTest + extends TestCase { + + public GillStepInterpolatorTest(String name) { + super(name); + } + + public void testSerialization() + throws DerivativeException, IntegratorException, + IOException, ClassNotFoundException { + + TestProblem3 pb = new TestProblem3(0.9); + double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.0003; + GillIntegrator integ = new GillIntegrator(step); + integ.setStepHandler(new ContinuousOutputModel()); + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos); + oos.writeObject(integ.getStepHandler()); + + assertTrue(bos.size () > 700000); + assertTrue(bos.size () < 701000); + + ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); + ObjectInputStream ois = new ObjectInputStream(bis); + ContinuousOutputModel cm = (ContinuousOutputModel) ois.readObject(); + + Random random = new Random(347588535632l); + double maxError = 0.0; + for (int i = 0; i < 1000; ++i) { + double r = random.nextDouble(); + double time = r * pb.getInitialTime() + (1.0 - r) * pb.getFinalTime(); + cm.setInterpolatedTime(time); + double[] interpolatedY = cm.getInterpolatedState (); + double[] theoreticalY = pb.computeTheoreticalState(time); + double dx = interpolatedY[0] - theoreticalY[0]; + double dy = interpolatedY[1] - theoreticalY[1]; + double error = dx * dx + dy * dy; + if (error > maxError) { + maxError = error; + } + } + + assertTrue(maxError < 0.003); + + } + + public static Test suite() { + return new TestSuite(GillStepInterpolatorTest.class); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/ode/GraggBulirschStoerIntegratorTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/GraggBulirschStoerIntegratorTest.java new file mode 100644 index 000000000..113fb7aa5 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/GraggBulirschStoerIntegratorTest.java @@ -0,0 +1,263 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +import junit.framework.*; + +public class GraggBulirschStoerIntegratorTest + extends TestCase { + + public GraggBulirschStoerIntegratorTest(String name) { + super(name); + } + + public void testDimensionCheck() { + try { + TestProblem1 pb = new TestProblem1(); + GraggBulirschStoerIntegrator integrator = + new GraggBulirschStoerIntegrator(0.0, 1.0, 1.0e-10, 1.0e-10); + integrator.integrate(pb, + 0.0, new double[pb.getDimension()+10], + 1.0, new double[pb.getDimension()+10]); + fail("an exception should have been thrown"); + } catch(DerivativeException de) { + fail("wrong exception caught"); + } catch(IntegratorException ie) { + } + } + + public void testNullIntervalCheck() { + try { + TestProblem1 pb = new TestProblem1(); + GraggBulirschStoerIntegrator integrator = + new GraggBulirschStoerIntegrator(0.0, 1.0, 1.0e-10, 1.0e-10); + integrator.integrate(pb, + 0.0, new double[pb.getDimension()], + 0.0, new double[pb.getDimension()]); + fail("an exception should have been thrown"); + } catch(DerivativeException de) { + fail("wrong exception caught"); + } catch(IntegratorException ie) { + } + } + + public void testMinStep() + throws DerivativeException, IntegratorException { + + try { + TestProblem1 pb = new TestProblem1(); + double minStep = 0.1 * (pb.getFinalTime() - pb.getInitialTime()); + double maxStep = pb.getFinalTime() - pb.getInitialTime(); + double absTolerance = 1.0e-20; + double relTolerance = 1.0e-20; + + FirstOrderIntegrator integ = + new GraggBulirschStoerIntegrator(minStep, maxStep, + absTolerance, relTolerance); + TestProblemHandler handler = new TestProblemHandler(pb); + integ.setStepHandler(handler); + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + fail("an exception should have been thrown"); + } catch(DerivativeException de) { + fail("wrong exception caught"); + } catch(IntegratorException ie) { + } + + } + + public void testIncreasingTolerance() + throws DerivativeException, IntegratorException { + + int previousCalls = Integer.MAX_VALUE; + for (int i = -12; i < -4; ++i) { + TestProblem1 pb = new TestProblem1(); + double minStep = 0; + double maxStep = pb.getFinalTime() - pb.getInitialTime(); + double absTolerance = Math.pow(10.0, i); + double relTolerance = absTolerance; + + FirstOrderIntegrator integ = + new GraggBulirschStoerIntegrator(minStep, maxStep, + absTolerance, relTolerance); + TestProblemHandler handler = new TestProblemHandler(pb); + integ.setStepHandler(handler); + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + // the coefficients are only valid for this test + // and have been obtained from trial and error + // there is no general relation between local and global errors + double ratio = handler.getMaximalError() / absTolerance; + assertTrue(ratio < 2.4); + assertTrue(ratio > 0.02); + + int calls = pb.getCalls(); + assertTrue(calls <= previousCalls); + previousCalls = calls; + + } + + } + + public void testSwitchingFunctions() + throws DerivativeException, IntegratorException { + + TestProblem4 pb = new TestProblem4(); + double minStep = 0; + double maxStep = pb.getFinalTime() - pb.getInitialTime(); + double scalAbsoluteTolerance = 1.0e-10; + double scalRelativeTolerance = 0.01 * scalAbsoluteTolerance; + + FirstOrderIntegrator integ = new GraggBulirschStoerIntegrator(minStep, maxStep, + scalAbsoluteTolerance, + scalRelativeTolerance); + TestProblemHandler handler = new TestProblemHandler(pb); + integ.setStepHandler(handler); + SwitchingFunction[] functions = pb.getSwitchingFunctions(); + if (functions != null) { + for (int l = 0; l < functions.length; ++l) { + integ.addSwitchingFunction(functions[l], + Double.POSITIVE_INFINITY, 1.0e-8 * maxStep); + } + } + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + assertTrue(handler.getMaximalError() < 5.0e-8); + assertEquals(12.0, handler.getLastTime(), 1.0e-8 * maxStep); + + } + + public void testKepler() + throws DerivativeException, IntegratorException { + + final TestProblem3 pb = new TestProblem3(0.9); + double minStep = 0; + double maxStep = pb.getFinalTime() - pb.getInitialTime(); + double absTolerance = 1.0e-6; + double relTolerance = 1.0e-6; + + FirstOrderIntegrator integ = + new GraggBulirschStoerIntegrator(minStep, maxStep, + absTolerance, relTolerance); + integ.setStepHandler(new StepHandler() { + private int nbSteps = 0; + private double maxError = 0; + public boolean requiresDenseOutput() { + return true; + } + public void reset() { + nbSteps = 0; + maxError = 0; + } + public void handleStep(StepInterpolator interpolator, + boolean isLast) + throws DerivativeException { + + ++nbSteps; + for (int a = 1; a < 100; ++a) { + + double prev = interpolator.getPreviousTime(); + double curr = interpolator.getCurrentTime(); + double interp = ((100 - a) * prev + a * curr) / 100; + interpolator.setInterpolatedTime(interp); + + double[] interpolatedY = interpolator.getInterpolatedState (); + double[] theoreticalY = pb.computeTheoreticalState(interpolator.getInterpolatedTime()); + double dx = interpolatedY[0] - theoreticalY[0]; + double dy = interpolatedY[1] - theoreticalY[1]; + double error = dx * dx + dy * dy; + if (error > maxError) { + maxError = error; + } + } + if (isLast) { + assertTrue(maxError < 2.7e-6); + assertTrue(nbSteps < 80); + } + } + }); + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + assertTrue(pb.getCalls() < 2150); + + } + + public void testVariableSteps() + throws DerivativeException, IntegratorException { + + final TestProblem3 pb = new TestProblem3(0.9); + double minStep = 0; + double maxStep = pb.getFinalTime() - pb.getInitialTime(); + double absTolerance = 1.0e-8; + double relTolerance = 1.0e-8; + FirstOrderIntegrator integ = + new GraggBulirschStoerIntegrator(minStep, maxStep, + absTolerance, relTolerance); + integ.setStepHandler(new StepHandler() { + private boolean firstTime = true; + private double minStep = 0; + private double maxStep = 0; + public boolean requiresDenseOutput() { + return false; + } + public void reset() { + firstTime = true; + minStep = 0; + maxStep = 0; + } + public void handleStep(StepInterpolator interpolator, + boolean isLast) { + + double step = Math.abs(interpolator.getCurrentTime() + - interpolator.getPreviousTime()); + if (firstTime) { + minStep = Math.abs(step); + maxStep = minStep; + firstTime = false; + } else { + if (step < minStep) { + minStep = step; + } + if (step > maxStep) { + maxStep = step; + } + } + + if (isLast) { + assertTrue(minStep < 8.2e-3); + assertTrue(maxStep > 1.7); + } + } + }); + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + } + + public static Test suite() { + return new TestSuite(GraggBulirschStoerIntegratorTest.class); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/ode/GraggBulirschStoerStepInterpolatorTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/GraggBulirschStoerStepInterpolatorTest.java new file mode 100644 index 000000000..e6ecfd3a2 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/GraggBulirschStoerStepInterpolatorTest.java @@ -0,0 +1,88 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +import junit.framework.*; +import java.util.Random; +import java.io.ByteArrayOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectInputStream; +import java.io.IOException; + +public class GraggBulirschStoerStepInterpolatorTest + extends TestCase { + + public GraggBulirschStoerStepInterpolatorTest(String name) { + super(name); + } + + public void testSerialization() + throws DerivativeException, IntegratorException, + IOException, ClassNotFoundException { + + TestProblem3 pb = new TestProblem3(0.9); + double minStep = 0; + double maxStep = pb.getFinalTime() - pb.getInitialTime(); + double absTolerance = 1.0e-8; + double relTolerance = 1.0e-8; + + GraggBulirschStoerIntegrator integ = + new GraggBulirschStoerIntegrator(minStep, maxStep, + absTolerance, relTolerance); + integ.setStepHandler(new ContinuousOutputModel()); + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos); + oos.writeObject(integ.getStepHandler()); + + assertTrue(bos.size () > 34000); + assertTrue(bos.size () < 35000); + + ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); + ObjectInputStream ois = new ObjectInputStream(bis); + ContinuousOutputModel cm = (ContinuousOutputModel) ois.readObject(); + + Random random = new Random(347588535632l); + double maxError = 0.0; + for (int i = 0; i < 1000; ++i) { + double r = random.nextDouble(); + double time = r * pb.getInitialTime() + (1.0 - r) * pb.getFinalTime(); + cm.setInterpolatedTime(time); + double[] interpolatedY = cm.getInterpolatedState (); + double[] theoreticalY = pb.computeTheoreticalState(time); + double dx = interpolatedY[0] - theoreticalY[0]; + double dy = interpolatedY[1] - theoreticalY[1]; + double error = dx * dx + dy * dy; + if (error > maxError) { + maxError = error; + } + } + + assertTrue(maxError < 5.0e-11); + + } + + public static Test suite() { + return new TestSuite(GraggBulirschStoerStepInterpolatorTest.class); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/ode/HighamHall54IntegratorTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/HighamHall54IntegratorTest.java new file mode 100644 index 000000000..9652a443b --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/HighamHall54IntegratorTest.java @@ -0,0 +1,182 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +import junit.framework.*; + +public class HighamHall54IntegratorTest + extends TestCase { + + public HighamHall54IntegratorTest(String name) { + super(name); + } + + public void testDimensionCheck() { + try { + TestProblem1 pb = new TestProblem1(); + HighamHall54Integrator integrator = new HighamHall54Integrator(0.0, 1.0, + 1.0e-10, 1.0e-10); + integrator.integrate(pb, + 0.0, new double[pb.getDimension()+10], + 1.0, new double[pb.getDimension()+10]); + fail("an exception should have been thrown"); + } catch(DerivativeException de) { + fail("wrong exception caught"); + } catch(IntegratorException ie) { + } + } + + public void testMinStep() + throws DerivativeException, IntegratorException { + + try { + TestProblem1 pb = new TestProblem1(); + double minStep = 0.1 * (pb.getFinalTime() - pb.getInitialTime()); + double maxStep = pb.getFinalTime() - pb.getInitialTime(); + double scalAbsoluteTolerance = 1.0e-15; + double scalRelativeTolerance = 1.0e-15; + + FirstOrderIntegrator integ = new HighamHall54Integrator(minStep, maxStep, + scalAbsoluteTolerance, + scalRelativeTolerance); + TestProblemHandler handler = new TestProblemHandler(pb); + integ.setStepHandler(handler); + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + fail("an exception should have been thrown"); + } catch(DerivativeException de) { + fail("wrong exception caught"); + } catch(IntegratorException ie) { + } + + } + + public void testIncreasingTolerance() + throws DerivativeException, IntegratorException { + + int previousCalls = Integer.MAX_VALUE; + for (int i = -12; i < -2; ++i) { + TestProblem1 pb = new TestProblem1(); + double minStep = 0; + double maxStep = pb.getFinalTime() - pb.getInitialTime(); + double scalAbsoluteTolerance = Math.pow(10.0, i); + double scalRelativeTolerance = 0.01 * scalAbsoluteTolerance; + + FirstOrderIntegrator integ = new HighamHall54Integrator(minStep, maxStep, + scalAbsoluteTolerance, + scalRelativeTolerance); + TestProblemHandler handler = new TestProblemHandler(pb); + integ.setStepHandler(handler); + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + // the 1.3 factor is only valid for this test + // and has been obtained from trial and error + // there is no general relation between local and global errors + assertTrue(handler.getMaximalError() < (1.3 * scalAbsoluteTolerance)); + + int calls = pb.getCalls(); + assertTrue(calls <= previousCalls); + previousCalls = calls; + + } + + } + + public void testSwitchingFunctions() + throws DerivativeException, IntegratorException { + + TestProblem4 pb = new TestProblem4(); + double minStep = 0; + double maxStep = pb.getFinalTime() - pb.getInitialTime(); + double scalAbsoluteTolerance = 1.0e-8; + double scalRelativeTolerance = 0.01 * scalAbsoluteTolerance; + + FirstOrderIntegrator integ = new HighamHall54Integrator(minStep, maxStep, + scalAbsoluteTolerance, + scalRelativeTolerance); + TestProblemHandler handler = new TestProblemHandler(pb); + integ.setStepHandler(handler); + SwitchingFunction[] functions = pb.getSwitchingFunctions(); + if (functions != null) { + for (int l = 0; l < functions.length; ++l) { + integ.addSwitchingFunction(functions[l], + Double.POSITIVE_INFINITY, 1.0e-8 * maxStep); + } + } + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + assertTrue(handler.getMaximalError() < 1.0e-7); + assertEquals(12.0, handler.getLastTime(), 1.0e-8 * maxStep); + + } + + public void testKepler() + throws DerivativeException, IntegratorException { + + final TestProblem3 pb = new TestProblem3(0.9); + double minStep = 0; + double maxStep = pb.getFinalTime() - pb.getInitialTime(); + double scalAbsoluteTolerance = 1.0e-8; + double scalRelativeTolerance = scalAbsoluteTolerance; + + FirstOrderIntegrator integ = new HighamHall54Integrator(minStep, maxStep, + scalAbsoluteTolerance, + scalRelativeTolerance); + integ.setStepHandler(new StepHandler() { + private int nbSteps = 0; + private double maxError = 0; + public boolean requiresDenseOutput() { + return false; + } + public void reset() { + nbSteps = 0; + maxError = 0; + } + public void handleStep(StepInterpolator interpolator, + boolean isLast) { + + ++nbSteps; + double[] interpolatedY = interpolator.getInterpolatedState (); + double[] theoreticalY = pb.computeTheoreticalState(interpolator.getCurrentTime()); + double dx = interpolatedY[0] - theoreticalY[0]; + double dy = interpolatedY[1] - theoreticalY[1]; + double error = dx * dx + dy * dy; + if (error > maxError) { + maxError = error; + } + if (isLast) { + assertTrue(maxError < 1.54e-10); + assertTrue(nbSteps < 520); + } + } + }); + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + } + + public static Test suite() { + return new TestSuite(HighamHall54IntegratorTest.class); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/ode/HighamHall54StepInterpolatorTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/HighamHall54StepInterpolatorTest.java new file mode 100644 index 000000000..fa522119f --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/HighamHall54StepInterpolatorTest.java @@ -0,0 +1,87 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +import junit.framework.*; +import java.util.Random; +import java.io.ByteArrayOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectInputStream; +import java.io.IOException; + +public class HighamHall54StepInterpolatorTest + extends TestCase { + + public HighamHall54StepInterpolatorTest(String name) { + super(name); + } + + public void testSerialization() + throws DerivativeException, IntegratorException, + IOException, ClassNotFoundException { + + TestProblem3 pb = new TestProblem3(0.9); + double minStep = 0; + double maxStep = pb.getFinalTime() - pb.getInitialTime(); + double scalAbsoluteTolerance = 1.0e-8; + double scalRelativeTolerance = scalAbsoluteTolerance; + HighamHall54Integrator integ = new HighamHall54Integrator(minStep, maxStep, + scalAbsoluteTolerance, + scalRelativeTolerance); + integ.setStepHandler(new ContinuousOutputModel()); + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos); + oos.writeObject(integ.getStepHandler()); + + assertTrue(bos.size () > 158000); + assertTrue(bos.size () < 159000); + + ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); + ObjectInputStream ois = new ObjectInputStream(bis); + ContinuousOutputModel cm = (ContinuousOutputModel) ois.readObject(); + + Random random = new Random(347588535632l); + double maxError = 0.0; + for (int i = 0; i < 1000; ++i) { + double r = random.nextDouble(); + double time = r * pb.getInitialTime() + (1.0 - r) * pb.getFinalTime(); + cm.setInterpolatedTime(time); + double[] interpolatedY = cm.getInterpolatedState (); + double[] theoreticalY = pb.computeTheoreticalState(time); + double dx = interpolatedY[0] - theoreticalY[0]; + double dy = interpolatedY[1] - theoreticalY[1]; + double error = dx * dx + dy * dy; + if (error > maxError) { + maxError = error; + } + } + + assertTrue(maxError < 1.6e-10); + + } + + public static Test suite() { + return new TestSuite(HighamHall54StepInterpolatorTest.class); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/ode/MidpointIntegratorTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/MidpointIntegratorTest.java new file mode 100644 index 000000000..7b85318f8 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/MidpointIntegratorTest.java @@ -0,0 +1,166 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +import junit.framework.*; + +import org.spaceroots.mantissa.estimation.EstimationException; +import org.spaceroots.mantissa.fitting.PolynomialFitter; + +public class MidpointIntegratorTest + extends TestCase { + + public MidpointIntegratorTest(String name) { + super(name); + } + + public void testDimensionCheck() { + try { + TestProblem1 pb = new TestProblem1(); + new MidpointIntegrator(0.01).integrate(pb, + 0.0, new double[pb.getDimension()+10], + 1.0, new double[pb.getDimension()+10]); + fail("an exception should have been thrown"); + } catch(DerivativeException de) { + fail("wrong exception caught"); + } catch(IntegratorException ie) { + } + } + + public void testDecreasingSteps() + throws DerivativeException, IntegratorException { + + TestProblemAbstract[] problems = TestProblemFactory.getProblems(); + for (int k = 0; k < problems.length; ++k) { + + double previousError = Double.NaN; + for (int i = 4; i < 10; ++i) { + + TestProblemAbstract pb = (TestProblemAbstract) problems[k].clone(); + double step = (pb.getFinalTime() - pb.getInitialTime()) + * Math.pow(2.0, -i); + FirstOrderIntegrator integ = new MidpointIntegrator(step); + TestProblemHandler handler = new TestProblemHandler(pb); + integ.setStepHandler(handler); + SwitchingFunction[] functions = pb.getSwitchingFunctions(); + if (functions != null) { + for (int l = 0; l < functions.length; ++l) { + integ.addSwitchingFunction(functions[l], + Double.POSITIVE_INFINITY, 1.0e-6 * step); + } + } + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + double error = handler.getMaximalError(); + if (i > 4) { + assertTrue(error < Math.abs(previousError)); + } + previousError = error; + } + + } + + } + + public void testOrder() + throws EstimationException, DerivativeException, IntegratorException { + PolynomialFitter fitter = new PolynomialFitter(1, + 10, 1.0e-7, 1.0e-10, + 1.0e-10); + + TestProblemAbstract[] problems = TestProblemFactory.getProblems(); + for (int k = 0; k < problems.length; ++k) { + + for (int i = 0; i < 10; ++i) { + + TestProblemAbstract pb = (TestProblemAbstract) problems[k].clone(); + double step = (pb.getFinalTime() - pb.getInitialTime()) + * Math.pow(2.0, -(i + 1)); + + FirstOrderIntegrator integ = new MidpointIntegrator(step); + TestProblemHandler handler = new TestProblemHandler(pb); + integ.setStepHandler(handler); + SwitchingFunction[] functions = pb.getSwitchingFunctions(); + if (functions != null) { + for (int l = 0; l < functions.length; ++l) { + integ.addSwitchingFunction(functions[l], + Double.POSITIVE_INFINITY, 1.0e-6 * step); + } + } + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + fitter.addWeightedPair(1.0, + Math.log(Math.abs(step)), + Math.log(handler.getLastError())); + + } + + // this is an order 2 method + double[] coeffs = fitter.fit(); + assertTrue(coeffs[1] > 1.2); + assertTrue(coeffs[1] < 2.8); + + } + + } + + public void testSmallStep() + throws DerivativeException, IntegratorException { + + TestProblem1 pb = new TestProblem1(); + double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.001; + + FirstOrderIntegrator integ = new MidpointIntegrator(step); + TestProblemHandler handler = new TestProblemHandler(pb); + integ.setStepHandler(handler); + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + assertTrue(handler.getLastError() < 2.0e-7); + assertTrue(handler.getMaximalError() < 1.0e-6); + + } + + public void testBigStep() + throws DerivativeException, IntegratorException { + + TestProblem1 pb = new TestProblem1(); + double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.2; + + FirstOrderIntegrator integ = new MidpointIntegrator(step); + TestProblemHandler handler = new TestProblemHandler(pb); + integ.setStepHandler(handler); + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + assertTrue(handler.getLastError() > 0.01); + assertTrue(handler.getMaximalError() > 0.05); + + } + + public static Test suite() { + return new TestSuite(MidpointIntegratorTest.class); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/ode/MidpointStepInterpolatorTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/MidpointStepInterpolatorTest.java new file mode 100644 index 000000000..4dd80fdda --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/MidpointStepInterpolatorTest.java @@ -0,0 +1,82 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +import junit.framework.*; +import java.util.Random; +import java.io.ByteArrayOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectInputStream; +import java.io.IOException; + +public class MidpointStepInterpolatorTest + extends TestCase { + + public MidpointStepInterpolatorTest(String name) { + super(name); + } + + public void testSerialization() + throws DerivativeException, IntegratorException, + IOException, ClassNotFoundException { + + TestProblem1 pb = new TestProblem1(); + double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.001; + MidpointIntegrator integ = new MidpointIntegrator(step); + integ.setStepHandler(new ContinuousOutputModel()); + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos); + oos.writeObject(integ.getStepHandler()); + + assertTrue(bos.size () > 98000); + assertTrue(bos.size () < 99000); + + ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); + ObjectInputStream ois = new ObjectInputStream(bis); + ContinuousOutputModel cm = (ContinuousOutputModel) ois.readObject(); + + Random random = new Random(347588535632l); + double maxError = 0.0; + for (int i = 0; i < 1000; ++i) { + double r = random.nextDouble(); + double time = r * pb.getInitialTime() + (1.0 - r) * pb.getFinalTime(); + cm.setInterpolatedTime(time); + double[] interpolatedY = cm.getInterpolatedState (); + double[] theoreticalY = pb.computeTheoreticalState(time); + double dx = interpolatedY[0] - theoreticalY[0]; + double dy = interpolatedY[1] - theoreticalY[1]; + double error = dx * dx + dy * dy; + if (error > maxError) { + maxError = error; + } + } + + assertTrue(maxError < 1.0e-6); + + } + + public static Test suite() { + return new TestSuite(MidpointStepInterpolatorTest.class); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/ode/StepNormalizerTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/StepNormalizerTest.java new file mode 100644 index 000000000..31abeb868 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/StepNormalizerTest.java @@ -0,0 +1,106 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +import junit.framework.*; + +public class StepNormalizerTest + extends TestCase { + + public StepNormalizerTest(String name) { + super(name); + } + + public void testBoundaries() + throws DerivativeException, IntegratorException { + double range = pb.getFinalTime() - pb.getInitialTime(); + setLastSeen(false); + integ.setStepHandler(new StepNormalizer(range / 10.0, + new FixedStepHandler() { + private boolean firstCall = true; + public void handleStep(double t, + double[] y, + boolean isLast) { + if (firstCall) { + checkValue(t, pb.getInitialTime()); + firstCall = false; + } + if (isLast) { + setLastSeen(true); + checkValue(t, pb.getFinalTime()); + } + } + })); + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + assertTrue(lastSeen); + } + + public void testBeforeEnd() + throws DerivativeException, IntegratorException { + final double range = pb.getFinalTime() - pb.getInitialTime(); + setLastSeen(false); + integ.setStepHandler(new StepNormalizer(range / 10.5, + new FixedStepHandler() { + public void handleStep(double t, + double[] y, + boolean isLast) { + if (isLast) { + setLastSeen(true); + checkValue(t, + pb.getFinalTime() - range / 21.0); + } + } + })); + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + assertTrue(lastSeen); + } + + public void checkValue(double value, double reference) { + assertTrue(Math.abs(value - reference) < 1.0e-10); + } + + public void setLastSeen(boolean lastSeen) { + this.lastSeen = lastSeen; + } + + public static Test suite() { + return new TestSuite(StepNormalizerTest.class); + } + + public void setUp() { + pb = new TestProblem3(0.9); + double minStep = 0; + double maxStep = pb.getFinalTime() - pb.getInitialTime(); + integ = new DormandPrince54Integrator(minStep, maxStep, 10.e-8, 1.0e-8); + lastSeen = false; + } + + public void tearDown() { + pb = null; + integ = null; + } + + TestProblem3 pb; + FirstOrderIntegrator integ; + boolean lastSeen; + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/ode/TestProblem1.java b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/TestProblem1.java new file mode 100644 index 000000000..3607155d4 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/TestProblem1.java @@ -0,0 +1,87 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +/** + * This class is used in the junit tests for the ODE integrators. + + *

      This specific problem is the following differential equation : + *

      + *    y' = -y
      + * 
      + * the solution of this equation is a simple exponential function : + *
      + *   y (t) = y (t0) exp (t0-t)
      + * 
      + *

      + + */ +class TestProblem1 + extends TestProblemAbstract { + + /** theoretical state */ + private double[] y; + + /** + * Simple constructor. + */ + public TestProblem1() { + super(); + double[] y0 = { 1.0, 0.1 }; + setInitialConditions(0.0, y0); + setFinalConditions(4.0); + double[] errorScale = { 1.0, 1.0 }; + setErrorScale(errorScale); + y = new double[y0.length]; + } + + /** + * Copy constructor. + * @param problem problem to copy + */ + public TestProblem1(TestProblem1 problem) { + super(problem); + y = new double[problem.y.length]; + System.arraycopy(problem.y, 0, y, 0, problem.y.length); + } + + /** + * Clone operation. + * @return a copy of the instance + */ + public Object clone() { + return new TestProblem1(this); + } + + public void doComputeDerivatives(double t, double[] y, double[] yDot) { + + // compute the derivatives + for (int i = 0; i < n; ++i) + yDot[i] = -y[i]; + + } + + public double[] computeTheoreticalState(double t) { + double c = Math.exp (t0 - t); + for (int i = 0; i < n; ++i) { + y[i] = c * y0[i]; + } + return y; + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/ode/TestProblem2.java b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/TestProblem2.java new file mode 100644 index 000000000..1c6f043a7 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/TestProblem2.java @@ -0,0 +1,89 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +/** + * This class is used in the junit tests for the ODE integrators. + + *

      This specific problem is the following differential equation : + *

      + *    y' = t^3 - t y
      + * 
      + * with the initial condition y (0) = 0. The solution of this equation + * is the following function : + *
      + *   y (t) = t^2 + 2 (ext (- t^2 / 2) - 1)
      + * 
      + *

      + + */ +class TestProblem2 + extends TestProblemAbstract { + + /** theoretical state */ + private double[] y; + + /** + * Simple constructor. + */ + public TestProblem2() { + super(); + double[] y0 = { 0.0 }; + setInitialConditions(0.0, y0); + setFinalConditions(1.0); + double[] errorScale = { 1.0 }; + setErrorScale(errorScale); + y = new double[y0.length]; + } + + /** + * Copy constructor. + * @param problem problem to copy + */ + public TestProblem2(TestProblem2 problem) { + super(problem); + y = new double[problem.y.length]; + System.arraycopy(problem.y, 0, y, 0, problem.y.length); + } + + /** + * Clone operation. + * @return a copy of the instance + */ + public Object clone() { + return new TestProblem2(this); + } + + public void doComputeDerivatives(double t, double[] y, double[] yDot) { + + // compute the derivatives + for (int i = 0; i < n; ++i) + yDot[i] = t * (t * t - y[i]); + + } + + public double[] computeTheoreticalState(double t) { + double t2 = t * t; + double c = t2 + 2 * (Math.exp (-0.5 * t2) - 1); + for (int i = 0; i < n; ++i) { + y[i] = c; + } + return y; + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/ode/TestProblem3.java b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/TestProblem3.java new file mode 100644 index 000000000..8a51113d9 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/TestProblem3.java @@ -0,0 +1,128 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +/** + * This class is used in the junit tests for the ODE integrators. + + *

      This specific problem is the following differential equation : + *

      + *    y1'' = -y1/r^3  y1 (0) = 1-e  y1' (0) = 0
      + *    y2'' = -y2/r^3  y2 (0) = 0    y2' (0) =sqrt((1+e)/(1-e))
      + *    r = sqrt (y1^2 + y2^2), e = 0.9
      + * 
      + * This is a two-body problem in the plane which can be solved by + * Kepler's equation + *
      + *   y1 (t) = ...
      + * 
      + *

      + + */ +class TestProblem3 + extends TestProblemAbstract { + + /** Eccentricity */ + double e; + + /** theoretical state */ + private double[] y; + + /** + * Simple constructor. + * @param e eccentricity + */ + public TestProblem3(double e) { + super(); + this.e = e; + double[] y0 = { 1 - e, 0, 0, Math.sqrt((1+e)/(1-e)) }; + setInitialConditions(0.0, y0); + setFinalConditions(20.0); + double[] errorScale = { 1.0, 1.0, 1.0, 1.0 }; + setErrorScale(errorScale); + y = new double[y0.length]; + } + + /** + * Simple constructor. + */ + public TestProblem3() { + this(0.1); + } + + /** + * Copy constructor. + * @param problem problem to copy + */ + public TestProblem3(TestProblem3 problem) { + super(problem); + e = problem.e; + y = new double[problem.y.length]; + System.arraycopy(problem.y, 0, y, 0, problem.y.length); + } + + /** + * Clone operation. + * @return a copy of the instance + */ + public Object clone() { + return new TestProblem3(this); + } + + public void doComputeDerivatives(double t, double[] y, double[] yDot) { + + // current radius + double r2 = y[0] * y[0] + y[1] * y[1]; + double invR3 = 1 / (r2 * Math.sqrt(r2)); + + // compute the derivatives + yDot[0] = y[2]; + yDot[1] = y[3]; + yDot[2] = -invR3 * y[0]; + yDot[3] = -invR3 * y[1]; + + } + + public double[] computeTheoreticalState(double t) { + + // solve Kepler's equation + double E = t; + double d = 0; + double corr = 0; + do { + double f2 = e * Math.sin(E); + double f0 = d - f2; + double f1 = 1 - e * Math.cos(E); + double f12 = f1 + f1; + corr = f0 * f12 / (f1 * f12 - f0 * f2); + d -= corr; + E = t + d; + } while (Math.abs(corr) > 1.0e-12); + + double cosE = Math.cos(E); + double sinE = Math.sin(E); + + y[0] = cosE - e; + y[1] = Math.sqrt(1 - e * e) * sinE; + y[2] = -sinE / (1 - e * cosE); + y[3] = Math.sqrt(1 - e * e) * cosE / (1 - e * cosE); + + return y; + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/ode/TestProblem4.java b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/TestProblem4.java new file mode 100644 index 000000000..4fe511b22 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/TestProblem4.java @@ -0,0 +1,134 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +/** + * This class is used in the junit tests for the ODE integrators. + + *

      This specific problem is the following differential equation : + *

      + *    x'' = -x
      + * 
      + * And when x decreases down to 0, the state should be changed as follows : + *
      + *   x' -> -x'
      + * 
      + * The theoretical solution of this problem is x = |sin(t+a)| + *

      + + */ +class TestProblem4 + extends TestProblemAbstract { + + /** Time offset. */ + private double a; + + /** theoretical state */ + private double[] y; + + /** Simple constructor. */ + public TestProblem4() { + super(); + a = 1.2; + double[] y0 = { Math.sin(a), Math.cos(a) }; + setInitialConditions(0.0, y0); + setFinalConditions(15); + double[] errorScale = { 1.0, 0.0 }; + setErrorScale(errorScale); + y = new double[y0.length]; + } + + /** + * Copy constructor. + * @param problem problem to copy + */ + public TestProblem4(TestProblem4 problem) { + super(problem); + a = problem.a; + y = new double[problem.y.length]; + System.arraycopy(problem.y, 0, y, 0, problem.y.length); + } + + /** + * Clone operation. + * @return a copy of the instance + */ + public Object clone() { + return new TestProblem4(this); + } + + public SwitchingFunction[] getSwitchingFunctions() { + return new SwitchingFunction[] { new Bounce(), new Stop() }; + } + + public void doComputeDerivatives(double t, double[] y, double[] yDot) { + yDot[0] = y[1]; + yDot[1] = -y[0]; + } + + public double[] computeTheoreticalState(double t) { + double sin = Math.sin(t + a); + double cos = Math.cos(t + a); + y[0] = Math.abs(sin); + y[1] = (sin >= 0) ? cos : -cos; + return y; + } + + private static class Bounce implements SwitchingFunction { + + private int sign; + + public Bounce() { + sign = +1; + } + + public double g(double t, double[] y) { + return sign * y[0]; + } + + public int eventOccurred(double t, double[] y) { + // this sign change is needed because the state will be reset soon + sign = -sign; + return SwitchingFunction.RESET; + } + + public void resetState(double t, double[] y) { + y[1] = -y[1]; + } + + } + + private static class Stop implements SwitchingFunction { + + public Stop() { + } + + public double g(double t, double[] y) { + return t - 12.0; + } + + public int eventOccurred(double t, double[] y) { + return SwitchingFunction.STOP; + } + + public void resetState(double t, double[] y) { + } + + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/ode/TestProblem5.java b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/TestProblem5.java new file mode 100644 index 000000000..f5c7cbb2d --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/TestProblem5.java @@ -0,0 +1,36 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +/** + * This class is used in the junit tests for the ODE integrators. + *

      This is the same as problem 1 except integration is done + * backward in time

      + */ +class TestProblem5 + extends TestProblem1 { + + /** + * Simple constructor. + */ + public TestProblem5() { + super(); + setFinalConditions(2 * t0 - t1); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/ode/TestProblemAbstract.java b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/TestProblemAbstract.java new file mode 100644 index 000000000..459795e8b --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/TestProblemAbstract.java @@ -0,0 +1,184 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +/** + * This class is used as the base class of the problems that are + * integrated during the junit tests for the ODE integrators. + */ +abstract class TestProblemAbstract + implements FirstOrderDifferentialEquations, Cloneable { + + /** Dimension of the problem. */ + protected int n; + + /** Number of functions calls. */ + protected int calls; + + /** Initial time */ + protected double t0; + + /** Initial state */ + protected double[] y0; + + /** Final time */ + protected double t1; + + /** Error scale */ + protected double[] errorScale; + + /** + * Simple constructor. + */ + protected TestProblemAbstract() { + n = 0; + calls = 0; + t0 = 0; + y0 = null; + t1 = 0; + errorScale = null; + } + + /** + * Copy constructor. + * @param problem problem to copy + */ + protected TestProblemAbstract(TestProblemAbstract problem) { + n = problem.n; + calls = problem.calls; + t0 = problem.t0; + if (problem.y0 == null) { + y0 = null; + } else { + y0 = new double[problem.y0.length]; + System.arraycopy(problem.y0, 0, y0, 0, problem.y0.length); + } + if (problem.errorScale == null) { + errorScale = null; + } else { + errorScale = new double[problem.errorScale.length]; + System.arraycopy(problem.errorScale, 0, errorScale, 0, + problem.errorScale.length); + } + t1 = problem.t1; + } + + /** + * Clone operation. + * @return a copy of the instance + */ + public abstract Object clone(); + + /** + * Set the initial conditions + * @param t0 initial time + * @param y0 initial state vector + */ + protected void setInitialConditions(double t0, double[] y0) { + calls = 0; + n = y0.length; + this.t0 = t0; + this.y0 = new double[y0.length]; + System.arraycopy(y0, 0, this.y0, 0, y0.length); + } + + /** + * Set the final conditions. + * @param t1 final time + */ + protected void setFinalConditions(double t1) { + this.t1 = t1; + } + + /** + * Set the error scale + * @param errorScale error scale + */ + protected void setErrorScale(double[] errorScale) { + this.errorScale = new double[errorScale.length]; + System.arraycopy(errorScale, 0, this.errorScale, 0, + errorScale.length); + } + + public int getDimension() { + return n; + } + + /** + * Get the initial time. + * @return initial time + */ + public double getInitialTime() { + return t0; + } + + /** + * Get the initial state vector. + * @return initial state vector + */ + public double[] getInitialState() { + return y0; + } + + /** + * Get the final time. + * @return final time + */ + public double getFinalTime() { + return t1; + } + + /** + * Get the error scale. + * @return error scale + */ + public double[] getErrorScale() { + return errorScale; + } + + /** + * Get the switching functions. + * @return switching functions + */ + public SwitchingFunction[] getSwitchingFunctions() { + return null; + } + + /** + * Get the number of calls. + * @return nuber of calls + */ + public int getCalls() { + return calls; + } + + public void computeDerivatives(double t, double[] y, double[] yDot) { + ++calls; + doComputeDerivatives(t, y, yDot); + } + + abstract public void doComputeDerivatives(double t, double[] y, double[] yDot); + + /** + * Compute the theoretical state at the specified time. + * @param t time at which the state is required + * @return state vector at time t + */ + abstract public double[] computeTheoreticalState(double t); + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/ode/TestProblemFactory.java b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/TestProblemFactory.java new file mode 100644 index 000000000..829021e5c --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/TestProblemFactory.java @@ -0,0 +1,49 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +/** + * This class is used in the junit tests for the ODE integrators. + */ +class TestProblemFactory { + + /** Problems pool. */ + private static TestProblemAbstract[] pool = { + new TestProblem1(), + new TestProblem2(), + new TestProblem3(), + new TestProblem4(), + new TestProblem5() + }; + + /** + * Private constructor. + * This is a utility class, so there are no instance at all. + */ + private TestProblemFactory() { + } + + /** + * Get the problems. + * @return array of problems to solve + */ + public static TestProblemAbstract[] getProblems() { + return pool; + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/ode/TestProblemHandler.java b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/TestProblemHandler.java new file mode 100644 index 000000000..5a59c3b4c --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/TestProblemHandler.java @@ -0,0 +1,121 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +/** + * This class is used to handle steps for the test problems + * integrated during the junit tests for the ODE integrators. + */ +class TestProblemHandler + implements StepHandler { + + /** Associated problem. */ + private TestProblemAbstract problem; + + /** Maximal error encountered during the integration. */ + private double maxError; + + /** Error at the end of the integration. */ + private double lastError; + + /** Time at the end of integration. */ + private double lastTime; + + /** + * Simple constructor. + * @param problem problem for which steps should be handled + */ + public TestProblemHandler(TestProblemAbstract problem) { + this.problem = problem; + reset(); + } + + public boolean requiresDenseOutput() { + return true; + } + + public void reset() { + maxError = 0; + lastError = 0; + } + + public void handleStep(StepInterpolator interpolator, + boolean isLast) + throws DerivativeException { + + double pT = interpolator.getPreviousTime(); + double cT = interpolator.getCurrentTime(); + double[] errorScale = problem.getErrorScale(); + + // store the error at the last step + if (isLast) { + double[] interpolatedY = interpolator.getInterpolatedState(); + double[] theoreticalY = problem.computeTheoreticalState(cT); + for (int i = 0; i < interpolatedY.length; ++i) { + double error = Math.abs(interpolatedY[i] - theoreticalY[i]); + if (error > lastError) { + lastError = error; + } + } + lastTime = cT; + } + + // walk through the step + for (int k = 0; k <= 20; ++k) { + + double time = pT + (k * (cT - pT)) / 20; + interpolator.setInterpolatedTime(time); + double[] interpolatedY = interpolator.getInterpolatedState(); + double[] theoreticalY = problem.computeTheoreticalState(interpolator.getInterpolatedTime()); + + // update the errors + for (int i = 0; i < interpolatedY.length; ++i) { + double error = errorScale[i] * Math.abs(interpolatedY[i] - theoreticalY[i]); + if (error > maxError) { + maxError = error; + } + } + + } + } + + /** + * Get the maximal error encountered during integration. + * @return maximal error + */ + public double getMaximalError() { + return maxError; + } + + /** + * Get the error at the end of the integration. + * @return error at the end of the integration + */ + public double getLastError() { + return lastError; + } + + /** + * Get the time at the end of the integration. + * @return time at the end of the integration. + */ + public double getLastTime() { + return lastTime; + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/ode/ThreeEighthesIntegratorTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/ThreeEighthesIntegratorTest.java new file mode 100644 index 000000000..544c48c90 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/ThreeEighthesIntegratorTest.java @@ -0,0 +1,203 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +import junit.framework.*; + +import org.spaceroots.mantissa.estimation.EstimationException; +import org.spaceroots.mantissa.fitting.PolynomialFitter; + +public class ThreeEighthesIntegratorTest + extends TestCase { + + public ThreeEighthesIntegratorTest(String name) { + super(name); + } + + public void testDimensionCheck() { + try { + TestProblem1 pb = new TestProblem1(); + new ThreeEighthesIntegrator(0.01).integrate(pb, + 0.0, new double[pb.getDimension()+10], + 1.0, new double[pb.getDimension()+10]); + fail("an exception should have been thrown"); + } catch(DerivativeException de) { + fail("wrong exception caught"); + } catch(IntegratorException ie) { + } + } + + public void testDecreasingSteps() + throws DerivativeException, IntegratorException { + + TestProblemAbstract[] problems = TestProblemFactory.getProblems(); + for (int k = 0; k < problems.length; ++k) { + + double previousError = Double.NaN; + for (int i = 4; i < 10; ++i) { + + TestProblemAbstract pb = (TestProblemAbstract) problems[k].clone(); + double step = (pb.getFinalTime() - pb.getInitialTime()) + * Math.pow(2.0, -i); + + FirstOrderIntegrator integ = new ThreeEighthesIntegrator(step); + TestProblemHandler handler = new TestProblemHandler(pb); + integ.setStepHandler(handler); + SwitchingFunction[] functions = pb.getSwitchingFunctions(); + if (functions != null) { + for (int l = 0; l < functions.length; ++l) { + integ.addSwitchingFunction(functions[l], + Double.POSITIVE_INFINITY, 1.0e-6 * step); + } + } + integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + double error = handler.getMaximalError(); + if (i > 4) { + assertTrue(error < Math.abs(previousError)); + } + previousError = error; + } + + } + + } + + public void testOrder() + throws EstimationException, DerivativeException, + IntegratorException { + PolynomialFitter fitter = new PolynomialFitter(1, + 10, 1.0e-7, 1.0e-10, + 1.0e-10); + + TestProblemAbstract[] problems = TestProblemFactory.getProblems(); + for (int k = 0; k < problems.length; ++k) { + + for (int i = 0; i < 10; ++i) { + + TestProblemAbstract pb = (TestProblemAbstract) problems[k].clone(); + double step = (pb.getFinalTime() - pb.getInitialTime()) + * Math.pow(2.0, -(i + 1)); + + FirstOrderIntegrator integ = new ThreeEighthesIntegrator(step); + TestProblemHandler handler = new TestProblemHandler(pb); + integ.setStepHandler(handler); + SwitchingFunction[] functions = pb.getSwitchingFunctions(); + if (functions != null) { + for (int l = 0; l < functions.length; ++l) { + integ.addSwitchingFunction(functions[l], + Double.POSITIVE_INFINITY, 1.0e-6 * step); + } + } + integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + fitter.addWeightedPair(1.0, + Math.log(Math.abs(step)), + Math.log(handler.getLastError())); + + } + + // this is an order 4 method + double[] coeffs = fitter.fit(); + assertTrue(coeffs[1] > 3.2); + assertTrue(coeffs[1] < 4.8); + + } + + } + + public void testSmallStep() + throws DerivativeException, IntegratorException { + + TestProblem1 pb = new TestProblem1(); + double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.001; + + FirstOrderIntegrator integ = new ThreeEighthesIntegrator(step); + TestProblemHandler handler = new TestProblemHandler(pb); + integ.setStepHandler(handler); + integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + assertTrue(handler.getLastError() < 2.0e-13); + assertTrue(handler.getMaximalError() < 4.0e-12); + + } + + public void testBigStep() + throws DerivativeException, IntegratorException { + + TestProblem1 pb = new TestProblem1(); + double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.2; + + FirstOrderIntegrator integ = new ThreeEighthesIntegrator(step); + TestProblemHandler handler = new TestProblemHandler(pb); + integ.setStepHandler(handler); + integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + assertTrue(handler.getLastError() > 0.0004); + assertTrue(handler.getMaximalError() > 0.005); + + } + + public void testKepler() + throws DerivativeException, IntegratorException { + + final TestProblem3 pb = new TestProblem3(0.9); + double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.0003; + + FirstOrderIntegrator integ = new ThreeEighthesIntegrator(step); + integ.setStepHandler(new StepHandler() { + private double maxError = 0; + public boolean requiresDenseOutput() { + return false; + } + public void reset() { + maxError = 0; + } + public void handleStep(StepInterpolator interpolator, + boolean isLast) { + + double[] interpolatedY = interpolator.getInterpolatedState (); + double[] theoreticalY = pb.computeTheoreticalState(interpolator.getCurrentTime()); + double dx = interpolatedY[0] - theoreticalY[0]; + double dy = interpolatedY[1] - theoreticalY[1]; + double error = dx * dx + dy * dy; + if (error > maxError) { + maxError = error; + } + if (isLast) { + // even with more than 1000 evaluations per period, + // RK4 is not able to integrate such an eccentric + // orbit with a good accuracy + assertTrue(maxError > 0.005); + } + } + }); + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + } + + public static Test suite() { + return new TestSuite(ThreeEighthesIntegratorTest.class); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/ode/ThreeEighthesStepInterpolatorTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/ThreeEighthesStepInterpolatorTest.java new file mode 100644 index 000000000..72089950a --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/ode/ThreeEighthesStepInterpolatorTest.java @@ -0,0 +1,82 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.ode; + +import junit.framework.*; +import java.util.Random; +import java.io.ByteArrayOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectInputStream; +import java.io.IOException; + +public class ThreeEighthesStepInterpolatorTest + extends TestCase { + + public ThreeEighthesStepInterpolatorTest(String name) { + super(name); + } + + public void testSerialization() + throws DerivativeException, IntegratorException, + IOException, ClassNotFoundException { + + TestProblem3 pb = new TestProblem3(0.9); + double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.0003; + ThreeEighthesIntegrator integ = new ThreeEighthesIntegrator(step); + integ.setStepHandler(new ContinuousOutputModel()); + integ.integrate(pb, + pb.getInitialTime(), pb.getInitialState(), + pb.getFinalTime(), new double[pb.getDimension()]); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos); + oos.writeObject(integ.getStepHandler()); + + assertTrue(bos.size () > 700000); + assertTrue(bos.size () < 701000); + + ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); + ObjectInputStream ois = new ObjectInputStream(bis); + ContinuousOutputModel cm = (ContinuousOutputModel) ois.readObject(); + + Random random = new Random(347588535632l); + double maxError = 0.0; + for (int i = 0; i < 1000; ++i) { + double r = random.nextDouble(); + double time = r * pb.getInitialTime() + (1.0 - r) * pb.getFinalTime(); + cm.setInterpolatedTime(time); + double[] interpolatedY = cm.getInterpolatedState (); + double[] theoreticalY = pb.computeTheoreticalState(time); + double dx = interpolatedY[0] - theoreticalY[0]; + double dy = interpolatedY[1] - theoreticalY[1]; + double error = dx * dx + dy * dy; + if (error > maxError) { + maxError = error; + } + } + + assertTrue(maxError > 0.005); + + } + + public static Test suite() { + return new TestSuite(ThreeEighthesStepInterpolatorTest.class); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/optimization/AllTests.java b/src/mantissa/tests-src/org/spaceroots/mantissa/optimization/AllTests.java new file mode 100644 index 000000000..e1f2be61f --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/optimization/AllTests.java @@ -0,0 +1,36 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.optimization; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class AllTests { + + public static Test suite() { + + TestSuite suite= new TestSuite("org.spaceroots.mantissa.optimization"); + + suite.addTest(NelderMeadTest.suite()); + suite.addTest(MultiDirectionalTest.suite()); + + return suite; + + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/optimization/MultiDirectionalTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/optimization/MultiDirectionalTest.java new file mode 100644 index 000000000..8f672b3a6 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/optimization/MultiDirectionalTest.java @@ -0,0 +1,100 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.optimization; + +import junit.framework.*; + +public class MultiDirectionalTest + extends TestCase { + + public MultiDirectionalTest(String name) { + super(name); + } + + public void testRosenbrock() + throws CostException, NoConvergenceException { + + CostFunction rosenbrock = + new CostFunction() { + public double cost(double[] x) { + ++count; + double a = x[1] - x[0] * x[0]; + double b = 1.0 - x[0]; + return 100 * a * a + b * b; + } + }; + + count = 0; + PointCostPair optimum = + new MultiDirectional().minimizes(rosenbrock, 100, new ValueChecker(1.0e-3), + new double[] { -1.2, 1.0 }, + new double[] { 3.5, -2.3 }); + + assertTrue(count > 60); + assertTrue(optimum.getCost() > 0.02); + + } + + public void testPowell() + throws CostException, NoConvergenceException { + + CostFunction powell = + new CostFunction() { + public double cost(double[] x) { + ++count; + double a = x[0] + 10 * x[1]; + double b = x[2] - x[3]; + double c = x[1] - 2 * x[2]; + double d = x[0] - x[3]; + return a * a + 5 * b * b + c * c * c * c + 10 * d * d * d * d; + } + }; + + count = 0; + PointCostPair optimum = + new MultiDirectional().minimizes(powell, 1000, new ValueChecker(1.0e-3), + new double[] { 3.0, -1.0, 0.0, 1.0 }, + new double[] { 4.0, 0.0, 1.0, 2.0 }); + assertTrue(count > 850); + assertTrue(optimum.getCost() > 0.015); + + } + + private class ValueChecker implements ConvergenceChecker { + + public ValueChecker(double threshold) { + this.threshold = threshold; + } + + public boolean converged(PointCostPair[] simplex) { + PointCostPair smallest = simplex[0]; + PointCostPair largest = simplex[simplex.length - 1]; + return (largest.getCost() - smallest.getCost()) < threshold; + } + + private double threshold; + + }; + + public static Test suite() { + return new TestSuite(MultiDirectionalTest.class); + } + + private int count; + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/optimization/NelderMeadTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/optimization/NelderMeadTest.java new file mode 100644 index 000000000..f37639f38 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/optimization/NelderMeadTest.java @@ -0,0 +1,106 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.optimization; + +import junit.framework.*; + +public class NelderMeadTest + extends TestCase { + + public NelderMeadTest(String name) { + super(name); + } + + public void testRosenbrock() + throws CostException, NoConvergenceException { + + CostFunction rosenbrock = + new CostFunction() { + public double cost(double[] x) { + ++count; + double a = x[1] - x[0] * x[0]; + double b = 1.0 - x[0]; + return 100 * a * a + b * b; + } + }; + + count = 0; + PointCostPair optimum = + new NelderMead().minimizes(rosenbrock, 100, new ValueChecker(1.0e-3), + new double[] { -1.2, 1.0 }, + new double[] { 3.5, -2.3 }); + + assertTrue(count < 50); + assertEquals(0.0, optimum.getCost(), 6.0e-4); + assertEquals(1.0, optimum.getPoint()[0], 0.05); + assertEquals(1.0, optimum.getPoint()[1], 0.05); + + } + + public void testPowell() + throws CostException, NoConvergenceException { + + CostFunction powell = + new CostFunction() { + public double cost(double[] x) { + ++count; + double a = x[0] + 10 * x[1]; + double b = x[2] - x[3]; + double c = x[1] - 2 * x[2]; + double d = x[0] - x[3]; + return a * a + 5 * b * b + c * c * c * c + 10 * d * d * d * d; + } + }; + + count = 0; + PointCostPair optimum = + new NelderMead().minimizes(powell, 200, new ValueChecker(1.0e-3), + new double[] { 3.0, -1.0, 0.0, 1.0 }, + new double[] { 4.0, 0.0, 1.0, 2.0 }); + assertTrue(count < 150); + assertEquals(0.0, optimum.getCost(), 6.0e-4); + assertEquals(0.0, optimum.getPoint()[0], 0.07); + assertEquals(0.0, optimum.getPoint()[1], 0.07); + assertEquals(0.0, optimum.getPoint()[2], 0.07); + assertEquals(0.0, optimum.getPoint()[3], 0.07); + + } + + private class ValueChecker implements ConvergenceChecker { + + public ValueChecker(double threshold) { + this.threshold = threshold; + } + + public boolean converged(PointCostPair[] simplex) { + PointCostPair smallest = simplex[0]; + PointCostPair largest = simplex[simplex.length - 1]; + return (largest.getCost() - smallest.getCost()) < threshold; + } + + private double threshold; + + }; + + public static Test suite() { + return new TestSuite(NelderMeadTest.class); + } + + private int count; + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/quadrature/AllTests.java b/src/mantissa/tests-src/org/spaceroots/mantissa/quadrature/AllTests.java new file mode 100644 index 000000000..8d287d347 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/quadrature/AllTests.java @@ -0,0 +1,32 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.quadrature; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class AllTests +{ + public static Test suite () + { + TestSuite suite= new TestSuite ("org.spaceroots.mantissa.quadrature"); + suite.addTest (org.spaceroots.mantissa.quadrature.scalar.AllTests.suite ()); + suite.addTest (org.spaceroots.mantissa.quadrature.vectorial.AllTests.suite ()); + return suite; + } +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/quadrature/scalar/AllTests.java b/src/mantissa/tests-src/org/spaceroots/mantissa/quadrature/scalar/AllTests.java new file mode 100644 index 000000000..8c716827b --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/quadrature/scalar/AllTests.java @@ -0,0 +1,33 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.quadrature.scalar; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class AllTests { + public static Test suite() { + + TestSuite suite = new TestSuite("org.spaceroots.mantissa.quadrature.scalar"); + + suite.addTest(GaussLegendreIntegratorTest.suite()); + + return suite; + + } +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/quadrature/scalar/GaussLegendreIntegratorTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/quadrature/scalar/GaussLegendreIntegratorTest.java new file mode 100644 index 000000000..ec333a36a --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/quadrature/scalar/GaussLegendreIntegratorTest.java @@ -0,0 +1,99 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.quadrature.scalar; + +import org.spaceroots.mantissa.functions.scalar.ComputableFunction; +import org.spaceroots.mantissa.functions.FunctionException; + +import java.util.Random; + +import junit.framework.*; + +public class GaussLegendreIntegratorTest + extends TestCase { + + public GaussLegendreIntegratorTest(String name) { + super(name); + } + + public void testExactIntegration() + throws FunctionException { + Random random = new Random(86343623467878363l); + int order = 0; + while (true) { + GaussLegendreIntegrator integrator = new GaussLegendreIntegrator(order, + 7.0); + int availableOrder = integrator.getEvaluationsPerStep(); + if (availableOrder < order) { + // we have tested all available orders + return; + } + + // an order n Gauss-Legendre integrator integrates + // 2n-1 degree polynoms exactly + for (int degree = 0; degree <= 2 * availableOrder - 1; ++degree) { + for (int i = 0; i < 10; ++i) { + Polynom p = new Polynom(degree, random, 100.0); + double s0 = integrator.integrate(p, -5.0, 15.0); + double s1 = p.exactIntegration(-5.0, 15.0); + assertTrue(Math.abs(s0 - s1) < 1.0e-12 * (1.0 + Math.abs(s0))); + } + } + + ++order; + + } + } + + public static Test suite() { + return new TestSuite(GaussLegendreIntegratorTest.class); + } + + private class Polynom implements ComputableFunction { + public Polynom(int degree, Random random, double max) { + coeffs = new double[degree + 1]; + for (int i = 0; i <= degree; ++i) { + coeffs[i] = 2.0 * max * (random.nextDouble() - 0.5); + } + } + + public double valueAt(double t) + throws FunctionException { + double y = coeffs[coeffs.length - 1]; + for (int i = coeffs.length - 2; i >= 0; --i) { + y = y * t + coeffs[i]; + } + return y; + } + + public double exactIntegration(double a, double b) + throws FunctionException { + double yb = coeffs[coeffs.length - 1] / coeffs.length; + double ya = yb; + for (int i = coeffs.length - 2; i >= 0; --i) { + yb = yb * b + coeffs[i] / (i + 1); + ya = ya * a + coeffs[i] / (i + 1); + } + return yb * b - ya * a; + } + + private double[] coeffs; + + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/quadrature/vectorial/AllTests.java b/src/mantissa/tests-src/org/spaceroots/mantissa/quadrature/vectorial/AllTests.java new file mode 100644 index 000000000..8bc3ab10a --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/quadrature/vectorial/AllTests.java @@ -0,0 +1,33 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.quadrature.vectorial; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class AllTests { + public static Test suite() { + + TestSuite suite = new TestSuite("org.spaceroots.mantissa.quadrature.vectorial"); + + suite.addTest(GaussLegendreIntegratorTest.suite()); + + return suite; + + } +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/quadrature/vectorial/GaussLegendreIntegratorTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/quadrature/vectorial/GaussLegendreIntegratorTest.java new file mode 100644 index 000000000..87331ac83 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/quadrature/vectorial/GaussLegendreIntegratorTest.java @@ -0,0 +1,122 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.quadrature.vectorial; + +import org.spaceroots.mantissa.functions.vectorial.ComputableFunction; +import org.spaceroots.mantissa.functions.FunctionException; + +import java.util.Random; + +import junit.framework.*; + +public class GaussLegendreIntegratorTest + extends TestCase { + + public GaussLegendreIntegratorTest(String name) { + super(name); + } + + public void testExactIntegration() + throws FunctionException { + Random random = new Random(86343623467878363l); + int order = 0; + while (true) { + GaussLegendreIntegrator integrator = new GaussLegendreIntegrator(order, + 7.0); + int availableOrder = integrator.getEvaluationsPerStep(); + if (availableOrder < order) { + // we have tested all available orders + return; + } + + // an order n Gauss-Legendre integrator integrates + // 2n-1 degree polynoms exactly + for (int degree = 0; degree <= 2 * availableOrder - 1; ++degree) { + for (int i = 0; i < 10; ++i) { + Polynom p = new Polynom(degree, random, 100.0); + double[] s0 = integrator.integrate(p, -5.0, 15.0); + double[] s1 = p.exactIntegration(-5.0, 15.0); + for (int j = 0; j < p.getDimension(); ++j) { + assertTrue(Math.abs(s0[j] - s1[j]) < 1.0e-12 * (1.0 + Math.abs(s0[j]))); + } + } + } + + ++order; + + } + } + + public static Test suite() { + return new TestSuite(GaussLegendreIntegratorTest.class); + } + + private class Polynom implements ComputableFunction { + public Polynom (int degree, Random random, double max) { + coeffs0 = new double[degree + 1]; + coeffs1 = new double[degree + 1]; + for (int i = 0; i <= degree; ++i) { + coeffs0[i] = 2.0 * max * (random.nextDouble() - 0.5); + coeffs1[i] = 2.0 * max * (random.nextDouble() - 0.5); + } + } + + public int getDimension() { + return 2; + } + + public double[] valueAt(double t) + throws FunctionException { + double[] y = new double[2]; + y[0] = coeffs0[coeffs0.length - 1]; + for (int i = coeffs0.length - 2; i >= 0; --i) { + y[0] = y[0] * t + coeffs0[i]; + } + y[1] = coeffs1 [coeffs1.length - 1]; + for (int i = coeffs1.length - 2; i >= 0; --i) { + y[1] = y[1] * t + coeffs1[i]; + } + return y; + } + + public double[] exactIntegration(double a, double b) + throws FunctionException { + double[] res = new double[2]; + double yb = coeffs0[coeffs0.length - 1] / coeffs0.length; + double ya = yb; + for (int i = coeffs0.length - 2; i >= 0; --i) { + yb = yb * b + coeffs0[i] / (i + 1); + ya = ya * a + coeffs0[i] / (i + 1); + } + res[0] = yb * b - ya * a; + yb = coeffs1[coeffs1.length - 1] / coeffs1.length; + ya = yb; + for (int i = coeffs1.length - 2; i >= 0; --i) { + yb = yb * b + coeffs1[i] / (i + 1); + ya = ya * a + coeffs1[i] / (i + 1); + } + res[1] = yb * b - ya * a; + return res; + } + + private double[] coeffs0; + private double[] coeffs1; + + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/random/AllTests.java b/src/mantissa/tests-src/org/spaceroots/mantissa/random/AllTests.java new file mode 100644 index 000000000..8ede25c6c --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/random/AllTests.java @@ -0,0 +1,37 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.random; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class AllTests { + public static Test suite() { + + TestSuite suite = new TestSuite("org.spaceroots.mantissa.random"); + + suite.addTest(ScalarSampleStatisticsTest.suite()); + suite.addTest(VectorialSampleStatisticsTest.suite()); + suite.addTest(UniformRandomGeneratorTest.suite()); + suite.addTest(GaussianRandomGeneratorTest.suite()); + suite.addTest(UncorrelatedRandomVectorGeneratorTest.suite()); + suite.addTest(CorrelatedRandomVectorGeneratorTest.suite()); + return suite; + + } +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/random/CorrelatedRandomVectorGeneratorTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/random/CorrelatedRandomVectorGeneratorTest.java new file mode 100644 index 000000000..5716f6f18 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/random/CorrelatedRandomVectorGeneratorTest.java @@ -0,0 +1,111 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.random; + +import org.spaceroots.mantissa.linalg.Matrix; +import org.spaceroots.mantissa.linalg.GeneralMatrix; +import org.spaceroots.mantissa.linalg.SymetricalMatrix; + +import junit.framework.*; + +public class CorrelatedRandomVectorGeneratorTest + extends TestCase { + + public CorrelatedRandomVectorGeneratorTest(String name) { + super(name); + } + + public void testRank() { + assertEquals(3, generator.getRank()); + } + + public void testRootMatrix() { + Matrix b = generator.getRootMatrix(); + Matrix bbt = b.mul(b.getTranspose()); + for (int i = 0; i < covariance.getRows(); ++i) { + for (int j = 0; j < covariance.getColumns(); ++j) { + assertEquals(covariance.getElement(i, j), + bbt.getElement(i, j), + 1.0e-12); + } + } + } + + public void testMeanAndCovariance() { + + VectorialSampleStatistics sample = new VectorialSampleStatistics(); + for (int i = 0; i < 5000; ++i) { + sample.add(generator.nextVector()); + } + + double[] estimatedMean = sample.getMean(null); + SymetricalMatrix estimatedCovariance = sample.getCovarianceMatrix(null); + for (int i = 0; i < estimatedMean.length; ++i) { + assertEquals(mean[i], estimatedMean[i], 0.07); + for (int j = 0; j <= i; ++j) { + assertEquals(covariance.getElement(i, j), + estimatedCovariance.getElement(i, j), + 0.1 * (1.0 + Math.abs(mean[i])) * (1.0 + Math.abs(mean[j]))); + } + } + + } + + public void setUp() { + try { + mean = new double[] { 0.0, 1.0, -3.0, 2.3}; + + GeneralMatrix b = new GeneralMatrix(4, 3); + int counter = 0; + for (int i = 0; i < b.getRows(); ++i) { + for (int j = 0; j < b.getColumns(); ++j) { + b.setElement(i, j, 1.0 + 0.1 * ++counter); + } + } + Matrix bbt = b.mul(b.getTranspose()); + covariance = new SymetricalMatrix(mean.length); + for (int i = 0; i < covariance.getRows(); ++i) { + covariance.setElement(i, i, bbt.getElement(i, i)); + for (int j = 0; j < covariance.getColumns(); ++j) { + covariance.setElementAndSymetricalElement(i, j, + bbt.getElement(i, j)); + } + } + + GaussianRandomGenerator rawGenerator = new GaussianRandomGenerator(17399225432l); + generator = new CorrelatedRandomVectorGenerator(mean, covariance, rawGenerator); + } catch (NotPositiveDefiniteMatrixException e) { + fail("not positive definite matrix"); + } + } + + public void tearDown() { + mean = null; + covariance = null; + generator = null; + } + + public static Test suite() { + return new TestSuite(CorrelatedRandomVectorGeneratorTest.class); + } + + private double[] mean; + private SymetricalMatrix covariance; + private CorrelatedRandomVectorGenerator generator; + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/random/GaussianRandomGeneratorTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/random/GaussianRandomGeneratorTest.java new file mode 100644 index 000000000..2f0156db6 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/random/GaussianRandomGeneratorTest.java @@ -0,0 +1,43 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.random; + +import junit.framework.*; + +public class GaussianRandomGeneratorTest + extends TestCase { + + public GaussianRandomGeneratorTest(String name) { + super(name); + } + + public void testMeanAndStandardDeviation() { + GaussianRandomGenerator generator = new GaussianRandomGenerator(17399225432l); + ScalarSampleStatistics sample = new ScalarSampleStatistics(); + for (int i = 0; i < 10000; ++i) { + sample.add(generator.nextDouble()); + } + assertEquals(0.0, sample.getMean(), 0.012); + assertEquals(1.0, sample.getStandardDeviation(), 0.01); + } + + public static Test suite() { + return new TestSuite(GaussianRandomGeneratorTest.class); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/random/ScalarSampleStatisticsTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/random/ScalarSampleStatisticsTest.java new file mode 100644 index 000000000..8d8a2ac8f --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/random/ScalarSampleStatisticsTest.java @@ -0,0 +1,102 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.random; + +import junit.framework.*; + +public class ScalarSampleStatisticsTest + extends TestCase { + + public ScalarSampleStatisticsTest(String name) { + super(name); + } + + public void testBasicStats() { + + ScalarSampleStatistics sample = new ScalarSampleStatistics(); + for (int i = 0; i < points.length; ++i) { + sample.add(points[i]); + } + + assertEquals(points.length, sample.size()); + assertEquals(-5.0, sample.getMin(), 1.0e-12); + assertEquals(10.4, sample.getMax(), 1.0e-12); + assertEquals( 3.0, sample.getMean(), 1.0e-12); + assertEquals( 3.920034013457876, sample.getStandardDeviation(), + 1.0e-12); + + } + + public void testAddSample() { + + ScalarSampleStatistics all = new ScalarSampleStatistics(); + ScalarSampleStatistics even = new ScalarSampleStatistics(); + ScalarSampleStatistics odd = new ScalarSampleStatistics(); + for (int i = 0; i < points.length; ++i) { + all.add(points[i]); + if (i % 2 == 0) { + even.add(points[i]); + } else { + odd.add(points[i]); + } + } + + even.add(odd); + + assertEquals(all.size(), even.size()); + assertEquals(all.getMin(), even.getMin(), 1.0e-12); + assertEquals(all.getMax(), even.getMax(), 1.0e-12); + assertEquals(all.getMean(), even.getMean(), 1.0e-12); + assertEquals(all.getStandardDeviation(), even.getStandardDeviation(), + 1.0e-12); + + } + + public void testAddArray() { + + ScalarSampleStatistics loop = new ScalarSampleStatistics(); + ScalarSampleStatistics direct = new ScalarSampleStatistics(); + for (int i = 0; i < points.length; ++i) { + loop.add(points[i]); + } + direct.add(points); + + assertEquals(loop.size(), direct.size()); + assertEquals(loop.getMin(), direct.getMin(), 1.0e-12); + assertEquals(loop.getMax(), direct.getMax(), 1.0e-12); + assertEquals(loop.getMean(), direct.getMean(), 1.0e-12); + assertEquals(loop.getStandardDeviation(), direct.getStandardDeviation(), + 1.0e-12); + + } + + public void setUp() { + points = new double[] {1.0, 4.2, -5, 4.0, 2.9, 10.4, 0.0, 4.1, 4.2, 4.2}; + } + + public void tearDown() { + points = null; + } + + public static Test suite() { + return new TestSuite(ScalarSampleStatisticsTest.class); + } + + private double[] points; + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/random/UncorrelatedRandomVectorGeneratorTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/random/UncorrelatedRandomVectorGeneratorTest.java new file mode 100644 index 000000000..d263d808e --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/random/UncorrelatedRandomVectorGeneratorTest.java @@ -0,0 +1,75 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.random; + +import org.spaceroots.mantissa.linalg.SymetricalMatrix; + +import junit.framework.*; + +public class UncorrelatedRandomVectorGeneratorTest + extends TestCase { + + public UncorrelatedRandomVectorGeneratorTest(String name) { + super(name); + } + + public void testMeanAndCorrelation() { + + VectorialSampleStatistics sample = new VectorialSampleStatistics(); + for (int i = 0; i < 10000; ++i) { + sample.add(generator.nextVector()); + } + + double[] estimatedMean = sample.getMean(null); + double scale; + SymetricalMatrix estimatedCorrelation = sample.getCovarianceMatrix(null); + for (int i = 0; i < estimatedMean.length; ++i) { + assertEquals(mean[i], estimatedMean[i], 0.07); + for (int j = 0; j < i; ++j) { + scale = standardDeviation[i] * standardDeviation[j]; + assertEquals(0, estimatedCorrelation.getElement(i, j) / scale, 0.03); + } + scale = standardDeviation[i] * standardDeviation[i]; + assertEquals(1, estimatedCorrelation.getElement(i, i) / scale, 0.025); + } + + } + + public void setUp() { + mean = new double[] {0.0, 1.0, -3.0, 2.3}; + standardDeviation = new double[] {1.0, 2.0, 10.0, 0.1}; + generator = + new UncorrelatedRandomVectorGenerator(mean, standardDeviation, + new GaussianRandomGenerator(17399225432l)); + } + + public void tearDown() { + mean = null; + standardDeviation = null; + generator = null; + } + + public static Test suite() { + return new TestSuite(UncorrelatedRandomVectorGeneratorTest.class); + } + + private double[] mean; + private double[] standardDeviation; + private UncorrelatedRandomVectorGenerator generator; + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/random/UniformRandomGeneratorTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/random/UniformRandomGeneratorTest.java new file mode 100644 index 000000000..b48b420ec --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/random/UniformRandomGeneratorTest.java @@ -0,0 +1,43 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.random; + +import junit.framework.*; + +public class UniformRandomGeneratorTest + extends TestCase { + + public UniformRandomGeneratorTest(String name) { + super(name); + } + + public void testMeanAndStandardDeviation() { + UniformRandomGenerator generator = new UniformRandomGenerator(17399225432l); + ScalarSampleStatistics sample = new ScalarSampleStatistics(); + for (int i = 0; i < 1000; ++i) { + sample.add(generator.nextDouble()); + } + assertEquals(0.0, sample.getMean(), 0.07); + assertEquals(1.0, sample.getStandardDeviation(), 0.02); + } + + public static Test suite() { + return new TestSuite(UniformRandomGeneratorTest.class); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/random/VectorialSampleStatisticsTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/random/VectorialSampleStatisticsTest.java new file mode 100644 index 000000000..99e646161 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/random/VectorialSampleStatisticsTest.java @@ -0,0 +1,166 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.random; + +import org.spaceroots.mantissa.linalg.SymetricalMatrix; + +import junit.framework.*; + +public class VectorialSampleStatisticsTest + extends TestCase { + + public VectorialSampleStatisticsTest(String name) { + super(name); + } + + public void testSimplistic() { + VectorialSampleStatistics sample = new VectorialSampleStatistics(); + sample.add(new double[] {-1.0, 1.0}); + sample.add(new double[] { 1.0, -1.0}); + SymetricalMatrix c = sample.getCovarianceMatrix(null); + assertEquals( 2.0, c.getElement(0, 0), 1.0e-12); + assertEquals(-2.0, c.getElement(1, 0), 1.0e-12); + assertEquals( 2.0, c.getElement(1, 1), 1.0e-12); + } + + public void testBasicStats() { + + VectorialSampleStatistics sample = new VectorialSampleStatistics(); + for (int i = 0; i < points.length; ++i) { + sample.add(points[i]); + } + + assertEquals(points.length, sample.size()); + + double[] min = sample.getMin(); + double[] max = sample.getMax(); + double[] mean = sample.getMean(null); + SymetricalMatrix c = sample.getCovarianceMatrix(null); + + double[] refMin = new double[] {-0.70, 0.00, -3.10}; + double[] refMax = new double[] { 6.00, 2.30, 5.00}; + double[] refMean = new double[] { 1.78, 1.62, 3.12}; + double[][] refC = new double[][] { + { 8.0470, -1.9195, -3.4445}, + {-1.9195, 1.0470, 3.2795}, + {-3.4445, 3.2795, 12.2070} + }; + + for (int i = 0; i < min.length; ++i) { + assertEquals(refMin[i], min[i], 1.0e-12); + assertEquals(refMax[i], max[i], 1.0e-12); + assertEquals(refMean[i], mean[i], 1.0e-12); + for (int j = 0; j <= i; ++j) { + assertEquals(refC[i][j], c.getElement(i, j), 1.0e-12); + } + } + + } + + public void testAddSample() { + + VectorialSampleStatistics all = new VectorialSampleStatistics(); + VectorialSampleStatistics even = new VectorialSampleStatistics(); + VectorialSampleStatistics odd = new VectorialSampleStatistics(); + for (int i = 0; i < points.length; ++i) { + all.add(points[i]); + if (i % 2 == 0) { + even.add(points[i]); + } else { + odd.add(points[i]); + } + } + + even.add(odd); + + assertEquals(all.size(), even.size()); + + double[] min = even.getMin(); + double[] max = even.getMax(); + double[] mean = even.getMean(null); + SymetricalMatrix c = even.getCovarianceMatrix(null); + + double[] refMin = all.getMin(); + double[] refMax = all.getMax(); + double[] refMean = all.getMean(null); + SymetricalMatrix refC = all.getCovarianceMatrix(null); + + for (int i = 0; i < min.length; ++i) { + assertEquals(refMin[i], min[i], 1.0e-12); + assertEquals(refMax[i], max[i], 1.0e-12); + assertEquals(refMean[i], mean[i], 1.0e-12); + for (int j = 0; j <= i; ++j) { + assertEquals(refC.getElement(i, j), c.getElement(i, j), 1.0e-12); + } + } + + } + + public void testAddArray() { + + VectorialSampleStatistics loop = new VectorialSampleStatistics(); + VectorialSampleStatistics direct = new VectorialSampleStatistics(); + for (int i = 0; i < points.length; ++i) { + loop.add(points[i]); + } + direct.add(points); + + assertEquals(loop.size(), direct.size()); + + double[] min = direct.getMin(); + double[] max = direct.getMax(); + double[] mean = direct.getMean(null); + SymetricalMatrix c = direct.getCovarianceMatrix(null); + + double[] refMin = loop.getMin(); + double[] refMax = loop.getMax(); + double[] refMean = loop.getMean(null); + SymetricalMatrix refC = loop.getCovarianceMatrix(null); + + for (int i = 0; i < min.length; ++i) { + assertEquals(refMin[i], min[i], 1.0e-12); + assertEquals(refMax[i], max[i], 1.0e-12); + assertEquals(refMean[i], mean[i], 1.0e-12); + for (int j = 0; j <= i; ++j) { + assertEquals(refC.getElement(i, j), c.getElement(i, j), 1.0e-12); + } + } + + } + + public void setUp() { + points = new double[][] { + { 1.2, 2.3, 4.5}, + {-0.7, 2.3, 5.0}, + { 3.1, 0.0, -3.1}, + { 6.0, 1.2, 4.2}, + {-0.7, 2.3, 5.0} + }; + } + + public void tearDown() { + points = null; + } + + public static Test suite() { + return new TestSuite(VectorialSampleStatisticsTest.class); + } + + private double [][] points; + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/roots/AllTests.java b/src/mantissa/tests-src/org/spaceroots/mantissa/roots/AllTests.java new file mode 100644 index 000000000..e5e7654ce --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/roots/AllTests.java @@ -0,0 +1,33 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.roots; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class AllTests { + public static Test suite() { + + TestSuite suite = new TestSuite("org.spaceroots.mantissa.roots"); + + suite.addTest(BrentSolverTest.suite()); + + return suite; + + } +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/roots/BrentSolverTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/roots/BrentSolverTest.java new file mode 100644 index 000000000..38c8cacb9 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/roots/BrentSolverTest.java @@ -0,0 +1,71 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.roots; + +import org.spaceroots.mantissa.functions.FunctionException; + +import junit.framework.*; + +public class BrentSolverTest + extends TestCase { + + public BrentSolverTest(String name) { + super(name); + } + + public void testAlefeldPotraShi() + throws FunctionException { + + TestProblem[] problems = TestProblem.getAPSProblems(); + BrentSolver solver = new BrentSolver(); + + for (int i = 0; i < problems.length; ++i) { + TestProblem p = problems[i]; + double tol = 1.0e-10 * Math.abs(p.getExpectedRoot()); + assertTrue(solver.findRoot(p, new Checker(tol), 1000, + p.getA(), p.valueAt(p.getA()), + p.getB(), p.valueAt(p.getB()))); + assertTrue(p.checkResult(solver.getRoot(), tol)); + } + + } + + private class Checker implements ConvergenceChecker { + + private double tolerance; + + public Checker (double tolerance) { + this.tolerance = tolerance; + } + + public int converged (double xLow, double fLow, + double xHigh, double fHigh) { + return (Math.abs(xHigh - xLow) <= tolerance) + ? ((Math.abs(fLow) <= Math.abs(fHigh)) + ? ConvergenceChecker.LOW + : ConvergenceChecker.HIGH) + : ConvergenceChecker.NONE; + } + + } + + public static Test suite() { + return new TestSuite(BrentSolverTest.class); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/roots/TestProblem.java b/src/mantissa/tests-src/org/spaceroots/mantissa/roots/TestProblem.java new file mode 100644 index 000000000..0e451e3a8 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/roots/TestProblem.java @@ -0,0 +1,418 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.roots; + +import org.spaceroots.mantissa.functions.scalar.ComputableFunction; + +import java.util.ArrayList; + +/** This class implement a reference problem for junit tests. */ +public abstract class TestProblem implements ComputableFunction { + + private double a; + private double b; + private double expectedRoot; + + protected TestProblem(double a, double b, double expectedRoot) { + this.a = a; + this.b = b; + this.expectedRoot = expectedRoot; + } + + public double getA() { + return a; + } + + public double getB() { + return b; + } + + public double getExpectedRoot() { + return expectedRoot; + } + + public boolean checkResult(double foundRoot, double tol) { + return Math.abs(foundRoot - expectedRoot) <= tol; + } + + /** Get the reference problems from G. E. Alefeld, F. A. Potra and Y. Shi. */ + public static TestProblem[] getAPSProblems() { + + ArrayList problems = new ArrayList(); + + // problem 1 + problems.add(new APSProblem1(Math.PI / 2, Math.PI, 1.8954942670340)); + + // problems 2 to 11 + double[] roots2To11 = { + 3.0229153472731, 6.6837535608081, 11.238701655002, 19.676000080623, + 29.828227326505, 41.906116195289, 55.953595800143, 71.985665586588, + 90.008868539167, 110.02653274833 + }; + for (int k = 0, n = 1; n <= 10; ++n) { + problems.add(new APSProblems2To11(1.0e-9 + n * n, + (n+1) * (n+1) - 1.0e-9, + roots2To11[k++])); + } + + // problems 12 to 14 + problems.add(new APSProblems12To14( -40, -9.0, 31.0, 0.0)); + problems.add(new APSProblems12To14(-100, -9.0, 31.0, 0.0)); + problems.add(new APSProblems12To14(-200, -9.0, 31.0, 0.0)); + + // problems 15 to 17 + int[] n15 = { 4, 6, 8, 10, 12 }; + double[] roots15 = { + 0.66874030497642, 0.76472449133173, 0.81776543395794, + 0.85133992252078, 0.87448527222117 + }; + for (int k = 0; k < n15.length; ++k) { + problems.add(new APSProblems15To17(n15[k], 0.2, 0.0, 5.0, roots15[k])); + } + + int[] n16 = { 4, 6, 8, 10, 12 }; + for (int k = 0; k < n16.length; ++k) { + problems.add(new APSProblems15To17(n16[k], 1.0, 0.0, 5.0, 1.0)); + } + + int[] n17 = { 8, 10, 12, 14 }; + for (int k = 0; k < n17.length; ++k) { + problems.add(new APSProblems15To17(n17[k], 1.0, -0.95, 4.05, 1.0)); + } + + // problem 18 + problems.add(new APSProblem18(0.0, 1.5, 0.52359877559830)); + + // problem 19 + int[] n19 = { 1, 2, 3, 4, 5, 20, 40, 60, 80, 100 }; + double[] roots19 = { + 0.42247770964124, 0.30669941048320, 0.22370545765466, + 0.17171914751951, 0.13825715505682, 3.4657359020854e-2, + 1.7328679513999e-2, 1.1552453009332e-2, 8.6643397569993e-3, + 6.9314718055995e-3 + }; + for (int k = 0; k < n19.length; ++k) { + problems.add(new APSProblem19(n19[k], 0.0, 1.0, roots19[k])); + } + + // problem 20 + int[] n20 = { 5, 10, 20 }; + double[] roots20 = { + 3.8402551840622e-2, 9.9000099980005e-3, 2.4937500390620e-3 + }; + for (int k = 0; k < n20.length; ++k) { + problems.add(new APSProblem20(n20[k], 0.0, 1.0, roots20[k])); + } + + // problem 21 + int[] n21 = { 2, 5, 10, 15, 20 }; + double[] roots21 = { + 0.5, 0.34595481584824, 0.24512233375331, + 0.19554762353657, 0.16492095727644 + }; + for (int k = 0; k < n21.length; ++k) { + problems.add(new APSProblem21(n21[k], 0.0, 1.0, roots21[k])); + } + + // problem 22 + int[] n22 = { 1, 2, 4, 5, 8, 15, 20 }; + double[] roots22 = { + 0.27550804099948, 0.13775402049974, 1.0305283778156e-2, + 3.6171081789041e-3, 4.1087291849640e-4, 2.5989575892908e-5, + 7.6685951221853e-6 + }; + for (int k = 0; k < n22.length; ++k) { + problems.add(new APSProblem22(n22[k], 0.0, 1.0, roots22[k])); + } + + // problem 23 + int[] n23 = { 1, 5, 10, 15, 20 }; + double[] roots23 = { + 0.40105813754155, 0.51615351875793, 0.53952222690842, + 0.54818229434066, 0.55270466667849 + }; + for (int k = 0; k < n23.length; ++k) { + problems.add(new APSProblem23(n23[k], 0.0, 1.0, roots23[k])); + } + + // problem 24 + int[] n24 = { 2, 5, 15, 20 }; + for (int k = 0; k < n24.length; ++k) { + problems.add(new APSProblem24(n24[k], 0.01, 1, 1.0 / n24[k])); + } + + // problem 25 + int[] n25 = { + 2, 3, 4, 5, 6, + 7, 9, 11, 13, 15, + 17, 19, 21, 23, 25, + 27, 29, 31, 33 + }; + for (int k = 0; k < n25.length; ++k) { + problems.add(new APSProblem25(n25[k], 1.0, 100.0, n25[k])); + } + + // problem 26 + problems.add(new APSProblem26(-1.0, 4.0, 0.0)); + + // problem 27 + int[] n27 = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, + 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 + }; + for (int k = 0; k < n27.length; ++k) { + problems.add(new APSProblem27(n27[k], -10000.0, Math.PI / 2, + 0.62380651896161)); + } + + // problem 28 + int[] n28 = { + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000 }; + double[] roots28 = { + 5.9051305594220e-5, 5.6367155339937e-5, 5.3916409455592e-5, + 5.1669892394942e-5, 4.9603096699145e-5, 4.7695285287639e-5, + 4.5928793239949e-5, 4.4288479195665e-5, 4.2761290257883e-5, + 4.1335913915954e-5, 4.0002497338020e-5, 3.8752419296207e-5, + 3.7578103559958e-5, 3.6472865219959e-5, 3.5430783356532e-5, + 3.4446594929961e-5, 3.3515605877800e-5, 3.2633616249437e-5, + 3.1796856858426e-5, 3.1001935436965e-5, 3.0245790670210e-5, + 1.2277994232462e-5, 6.1695393904409e-6, 4.1198585298293e-6, + 3.0924623877272e-6, 2.4752044261050e-6, 2.0633567678513e-6, + 1.7690120078154e-6, 1.5481615698859e-6, 1.3763345366022e-6, + 1.2388385788997e-6 + }; + for (int k = 0; k < n28.length; ++k) { + problems.add(new APSProblem28(n28[k], -10000.0, 10000.0, roots28[k])); + } + + return (TestProblem[]) problems.toArray(new TestProblem[0]); + + } + + private static class APSProblem1 extends TestProblem { + public APSProblem1(double a, double b, double expectedRoot) { + super(a, b, expectedRoot); + } + public double valueAt(double x) { + return Math.sin(x) - x / 2; + } + } + + private static class APSProblems2To11 extends TestProblem { + public APSProblems2To11(double a, double b, double expectedRoot) { + super(a, b, expectedRoot); + } + public double valueAt(double x) { + double f = 0; + for (int i = 1; i <= 20; ++i) { + double n = 2.0 * i - 5.0; + double d = x - i * i; + f += n * n / (d * d * d); + } + return -2 * f; + } + } + + private static class APSProblems12To14 extends TestProblem { + private int n; + public APSProblems12To14(int n, double a, double b, double expectedRoot) { + super(a, b, expectedRoot); + this.n = n; + } + public double valueAt(double x) { + return n * x * Math.exp(-x); + } + } + + private static class APSProblems15To17 extends TestProblem { + private int n; + private double u; + public APSProblems15To17(int n, double u, + double a, double b, double expectedRoot) { + super(a, b, expectedRoot); + this.n = n; + this.u = u; + } + public double valueAt(double x) { + return Math.pow(x, n) - u; + } + } + + private static class APSProblem18 extends TestProblem { + public APSProblem18(double a, double b, double expectedRoot) { + super(a, b, expectedRoot); + } + public double valueAt(double x) { + return Math.sin(x) - 0.5; + } + } + + private static class APSProblem19 extends TestProblem { + private int n; + public APSProblem19(int n, double a, double b, double expectedRoot) { + super(a, b, expectedRoot); + this.n = n; + } + public double valueAt(double x) { + return 2.0 * x * Math.exp(-n) - 2.0 *Math.exp(-n * x) + 1.0; + } + } + + private static class APSProblem20 extends TestProblem { + private int n; + private int oPoMn2; + public APSProblem20(int n, double a, double b, double expectedRoot) { + super(a, b, expectedRoot); + this.n = n; + int oMn = 1 - n; + oPoMn2 = 1 + oMn * oMn; + } + public double valueAt(double x) { + double v = 1.0 - n * x; + return oPoMn2 * x - v * v; + } + } + + private static class APSProblem21 extends TestProblem { + private int n; + public APSProblem21(int n, double a, double b, double expectedRoot) { + super(a, b, expectedRoot); + this.n = n; + } + public double valueAt(double x) { + return x * x - Math.pow(1 - x, n); + } + } + + private static class APSProblem22 extends TestProblem { + private int n; + private int oPoMn4; + public APSProblem22(int n, double a, double b, double expectedRoot) { + super(a, b, expectedRoot); + this.n = n; + int oMn = 1 - n; + int oMn2 = oMn * oMn; + oPoMn4 = 1 + oMn2 * oMn2; + } + public double valueAt(double x) { + double oMnx = 1 - n * x; + double oMnx2 = oMnx * oMnx; + return oPoMn4 * x - oMnx2 * oMnx2; + } + } + + private static class APSProblem23 extends TestProblem { + private int n; + public APSProblem23(int n, double a, double b, double expectedRoot) { + super(a, b, expectedRoot); + this.n = n; + } + public double valueAt(double x) { + return (x - 1.0) * Math.exp(-n * x) + Math.pow(x, n); + } + } + + private static class APSProblem24 extends TestProblem { + private int n; + public APSProblem24(int n, double a, double b, double expectedRoot) { + super(a, b, expectedRoot); + this.n = n; + } + public double valueAt(double x) { + return (n * x - 1.0) / ((n - 1) * x); + } + } + + private static class APSProblem25 extends TestProblem { + private double u; + private double v;; + public APSProblem25(int n, double a, double b, double expectedRoot) { + super(a, b, expectedRoot); + u = 1.0 / n; + v = Math.pow(n, u); + } + public double valueAt(double x) { + return Math.pow(x, u) - v; + } + } + + private static class APSProblem26 extends TestProblem { + public APSProblem26(double a, double b, double expectedRoot) { + super(a, b, expectedRoot); + } + public double valueAt(double x) { + if (x == 0.0) { + return 0; + } + return x / Math.exp(1 / (x * x)); + } + + // this is a very special case since there is a wide range around + // the true root (which is 0) for which |f(x)| is smaller than the + // smallest representable positive number (according to IEEE 754): + // f(0.03762210865...) = 2^-1024 + // f(0.03764056462...) = 2^-1023 + // f(0.03765904777...) = 2^-1022 + // f(0.03767755816...) = 2^-1021 + // any root between -0.03768 and +0.03768 should be considered good + public boolean checkResult(double foundRoot, double tol) { + return Math.abs(foundRoot) <= 0.03768; + } + + } + + private static class APSProblem27 extends TestProblem { + private double u; + public APSProblem27(int n, double a, double b, double expectedRoot) { + super(a, b, expectedRoot); + u = n / 20.0; + } + public double valueAt(double x) { + if (x >= 0.0) { + return (x / 1.5 + Math.sin(x) - 1.0) * u; + } + return -u; + } + } + + private static class APSProblem28 extends TestProblem { + private double threshold; + private double yHigh; + private int u; + public APSProblem28(int n, double a, double b, double expectedRoot) { + super(a, b, expectedRoot); + threshold = 0.002 / (1 + n); + yHigh = Math.exp(1.0) - 1.859; + u = (n + 1) * 500; + } + public double valueAt(double x) { + if (x >= threshold) { + return yHigh; + } else if (x >= 0) { + return Math.exp(u * x) - 1.859; + } else { + return -0.859; + } + } + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/utilities/AllTests.java b/src/mantissa/tests-src/org/spaceroots/mantissa/utilities/AllTests.java new file mode 100644 index 000000000..d8183cd23 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/utilities/AllTests.java @@ -0,0 +1,37 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.utilities; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class AllTests { + public static Test suite() { + + TestSuite suite = new TestSuite("org.spaceroots.mantissa.utilities"); + + suite.addTest(ArrayMapperTest.suite()); + suite.addTest(MappableArrayTest.suite()); + suite.addTest(MappableScalarTest.suite()); + suite.addTest(IntervalTest.suite()); + suite.addTest(IntervalsListTest.suite()); + + return suite; + + } +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/utilities/ArrayMapperTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/utilities/ArrayMapperTest.java new file mode 100644 index 000000000..550adea1b --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/utilities/ArrayMapperTest.java @@ -0,0 +1,146 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.utilities; + +import junit.framework.*; + +public class ArrayMapperTest + extends TestCase { + + public ArrayMapperTest(String name) { + super(name); + mapper = null; + } + + public void testDimensionCheck() { + int size = b1.getStateDimension(); + size += b2.getStateDimension(); + size += b3.getStateDimension(); + assertTrue(mapper.getInternalDataArray().length == size); + } + + public void testUpdateObjects() { + + double[] data = new double [7]; + for (int i = 0; i < 7; ++i) { + data [i] = i * 0.1; + } + + mapper.updateObjects(data); + + assertTrue(Math.abs(b1.getElement(0) - 0.0) < 1.0e-10); + + assertTrue(Math.abs(b2.getElement(0) - 0.4) < 1.0e-10); + assertTrue(Math.abs(b2.getElement(1) - 0.3) < 1.0e-10); + assertTrue(Math.abs(b2.getElement(2) - 0.2) < 1.0e-10); + assertTrue(Math.abs(b2.getElement(3) - 0.1) < 1.0e-10); + + assertTrue(Math.abs(b3.getElement(0) - 0.6) < 1.0e-10); + assertTrue(Math.abs(b3.getElement(1) - 0.5) < 1.0e-10); + + } + + public void testUpdateArray() { + + b1.setElement(0, 0.0); + + b2.setElement(0, 40.0); + b2.setElement(1, 30.0); + b2.setElement(2, 20.0); + b2.setElement(3, 10.0); + + b3.setElement(0, 60.0); + b3.setElement(1, 50.0); + + mapper.updateArray(); + + double[] data = mapper.getInternalDataArray(); + for (int i = 0; i < 7; ++i) { + assertTrue(Math.abs(data [i] - i * 10.0) < 1.0e-10); + } + + } + + public void setUp() { + + b1 = new DomainObject(1); + b2 = new DomainObject(4); + b3 = new DomainObject(2); + + mapper = new ArrayMapper(); + mapper.manageMappable(b1); + mapper.manageMappable(b2); + mapper.manageMappable(b3); + + } + + public void tearOff() { + + b1 = null; + b2 = null; + b3 = null; + + mapper = null; + + } + + public static Test suite() { + return new TestSuite(ArrayMapperTest.class); + } + + private class DomainObject implements ArraySliceMappable { + + private double[] data; + + public DomainObject(int size) { + data = new double [size]; + } + + public int getStateDimension() { + return data.length; + } + + public void mapStateFromArray(int start, double[] array) { + for (int i = 0; i < data.length; ++i) { + data [data.length - 1 - i] = array [start + i]; + } + } + + public void mapStateToArray(int start, double[] array) { + for (int i = 0; i < data.length; ++i) { + array [start + i] = data [data.length - 1 - i]; + } + } + + public double getElement(int i) { + return data [i]; + } + + public void setElement(int i, double value) { + data [i] = value; + } + + } + + private DomainObject b1; + private DomainObject b2; + private DomainObject b3; + + private ArrayMapper mapper; + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/utilities/IntervalTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/utilities/IntervalTest.java new file mode 100644 index 000000000..7a0e2286d --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/utilities/IntervalTest.java @@ -0,0 +1,88 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.utilities; + +import junit.framework.*; + +public class IntervalTest + extends TestCase { + + public IntervalTest(String name) { + super(name); + } + + public void test1() { + check(new Interval(-10.0, 10.0), new Interval(11.0, 12.0), 2.5, + true, false, false, + new Interval(-10.0, 12.0), new Interval(11.0, 11.0)); + } + + public void test2() { + check(new Interval(-10.0, 10.0), new Interval(9.0, 12.0), 50.0, + false, false, true, + new Interval(-10.0, 12.0), new Interval(9.0, 10.0)); + } + + public void test3() { + check(new Interval(-10.0, 10.0), new Interval(-12.0, -11.0), 0.0, + true, false, false, + new Interval(-12.0, 10.0), new Interval(-10.0, -10.0)); + } + + public void test4() { + check(new Interval(-10.0, 10.0), new Interval(-4.0, 5.0), 0.0, + true, true, true, + new Interval(-10.0, 10.0), new Interval(-4.0, 5.0)); + } + + public void test5() { + check(new Interval(-10.0, 10.0), new Interval(-10.0, 10.0), 0.0, + true, true, true, + new Interval(-10.0, 10.0), new Interval(-10.0, 10.0)); + } + + private void check(Interval i1, Interval i2, double x, + boolean b1, boolean b2, boolean b3, + Interval add, Interval inter) { + + assertTrue(i1.contains(x) ^ (!b1)); + assertTrue(i1.contains(i2) ^ (!b2)); + assertTrue(i1.intersects(i2) ^ (!b3)); + + assertEquals(add.getInf(), Interval.add(i1, i2).getInf(), 1.0e-10); + assertEquals(add.getSup(), Interval.add(i1, i2).getSup(), 1.0e-10); + assertEquals(inter.getInf(), Interval.intersection(i1, i2).getInf(), 1.0e-10); + assertEquals(inter.getSup(), Interval.intersection(i1, i2).getSup(), 1.0e-10); + + Interval ia = new Interval(i1); + ia.addToSelf(i2); + assertEquals(add.getInf(), ia.getInf(), 1.0e-10); + assertEquals(add.getSup(), ia.getSup(), 1.0e-10); + + Interval ib = new Interval(i1); + ib.intersectSelf(i2); + assertEquals(inter.getInf(), ib.getInf(), 1.0e-10); + assertEquals(inter.getSup(), ib.getSup(), 1.0e-10); + + } + + public static Test suite() { + return new TestSuite(IntervalTest.class); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/utilities/IntervalsListTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/utilities/IntervalsListTest.java new file mode 100644 index 000000000..80f1873b8 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/utilities/IntervalsListTest.java @@ -0,0 +1,120 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.utilities; + +import java.util.Iterator; + +import junit.framework.*; + +public class IntervalsListTest + extends TestCase { + + public IntervalsListTest(String name) { + super(name); + } + + public void testAddBetween() { + IntervalsList il = + new IntervalsList (new Interval(10, 20), new Interval(50, 60)); + il.addToSelf(new Interval(30, 40)); + checkEquals(new Interval[] { + new Interval(10, 20), + new Interval(30, 40), + new Interval(50, 60) + }, il); + } + + public void testAddReducingLastHole() { + IntervalsList il = + new IntervalsList (new Interval(10, 20), new Interval(50, 60)); + il.addToSelf(new Interval(30, 55)); + checkEquals(new Interval[] { + new Interval(10, 20), + new Interval(30, 60) + }, il); + } + + public void test1() { + + IntervalsList list1 = new IntervalsList(-2.0, -1.0); + IntervalsList list2 = new IntervalsList(new Interval(-0.9, -0.8)); + check(list1, list2, 2.5, + true, false, 1, true, false, 1, false, + new Interval[] { new Interval(-2.0, -1.0), + new Interval(-0.9, -0.8) }, + new Interval[0]); + + list2.addToSelf(new Interval(1.0, 3.0)); + check(list1, list2, 2.5, + true, false, 1, false, false, 2, false, + new Interval[] { new Interval(-2.0, -1.0), + new Interval(-0.9, -0.8), + new Interval( 1.0, 3.0) }, + new Interval[0]); + + list1.addToSelf(new Interval(-1.2, 0.0)); + check(list1, list2, -1.1, + true, false, 1, false, false, 2, true, + new Interval[] { new Interval(-2.0, 0.0), + new Interval( 1.0, 3.0) }, + new Interval[] { new Interval(-0.9, -0.8) }); + + IntervalsList list = new IntervalsList(new Interval(-10.0, -8.0)); + list.addToSelf(new Interval(-6.0, -4.0)); + list.addToSelf(new Interval(-0.85, 1.2)); + list1.addToSelf(list); + check(list1, list2, 0, + false, false, 3, false, false, 2, true, + new Interval[] { new Interval(-10.0, -8.0), + new Interval( -6.0, -4.0), + new Interval( -2.0, 3.0) }, + new Interval[] { new Interval( -0.9, -0.8), + new Interval( 1.0, 1.2) }); + + } + + private void check(IntervalsList l1, IntervalsList l2, double x, + boolean b1, boolean b2, int i1, + boolean b3, boolean b4, int i2, + boolean b5, Interval[] add, Interval[] inter) { + assertTrue(l1.isConnex() ^ (!b1)); + assertTrue(l1.isEmpty() ^ (!b2)); + assertEquals(i1, l1.getIntervals().size()); + assertTrue(l2.isConnex() ^ (!b3)); + assertTrue(l2.isEmpty() ^ (!b4)); + assertEquals(i2, l2.getIntervals().size()); + assertTrue(l1.contains(x) ^ (!b5)); + checkEquals(add, IntervalsList.add(l1, l2)); + checkEquals(inter, IntervalsList.intersection(l1, l2)); + } + + private void checkEquals(Interval[] sa, IntervalsList sb) { + assertEquals(sa.length, sb.getIntervals().size()); + Iterator iterB = sb.getIntervals().iterator(); + for (int i = 0; i < sa.length; ++i) { + Interval ib = (Interval) iterB.next(); + assertEquals(sa[i].getInf(), ib.getInf(), 1.0e-10); + assertEquals(sa[i].getSup(), ib.getSup(), 1.0e-10); + } + } + + public static Test suite() { + return new TestSuite(IntervalsListTest.class); + } + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/utilities/MappableArrayTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/utilities/MappableArrayTest.java new file mode 100644 index 000000000..9a795f212 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/utilities/MappableArrayTest.java @@ -0,0 +1,146 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.utilities; + +import junit.framework.*; + +public class MappableArrayTest + extends TestCase { + + public MappableArrayTest(String name) { + super(name); + } + + public void testDimensionCheck() { + assertTrue(mapper.getInternalDataArray().length == 9); + } + + public void testRealloc() { + + for (int i = 0; i < reusedArray.length; ++i) { + reusedArray[i] = -1.0; + } + + for (int i = 0; i < clonedArray.length; ++i) { + clonedArray[i] = -1.0; + } + + double[] data = new double [mapper.getInternalDataArray().length]; + for (int i = 0; i < data.length; ++i) { + data [i] = i * 0.1; + } + + mapper.updateObjects(data); + + assertTrue(Math.abs(reusedArray[0] - 0.4) < 1.0e-10); + assertTrue(Math.abs(reusedArray[1] - 0.5) < 1.0e-10); + + assertTrue(Math.abs(clonedArray[0] + 1.0) < 1.0e-10); + assertTrue(Math.abs(clonedArray[1] + 1.0) < 1.0e-10); + assertTrue(Math.abs(clonedArray[2] + 1.0) < 1.0e-10); + + } + + public void testUpdateObjects() { + + double[] data = new double [mapper.getInternalDataArray().length]; + for (int i = 0; i < data.length; ++i) { + data [i] = i * 0.1; + } + + mapper.updateObjects(data); + + assertTrue(Math.abs(array1.getArray()[0] - 0.0) < 1.0e-10); + assertTrue(Math.abs(array1.getArray()[1] - 0.1) < 1.0e-10); + assertTrue(Math.abs(array1.getArray()[2] - 0.2) < 1.0e-10); + assertTrue(Math.abs(array1.getArray()[3] - 0.3) < 1.0e-10); + + assertTrue(Math.abs(array2.getArray()[0] - 0.4) < 1.0e-10); + assertTrue(Math.abs(array2.getArray()[1] - 0.5) < 1.0e-10); + + assertTrue(Math.abs(array3.getArray()[0] - 0.6) < 1.0e-10); + assertTrue(Math.abs(array3.getArray()[1] - 0.7) < 1.0e-10); + assertTrue(Math.abs(array3.getArray()[2] - 0.8) < 1.0e-10); + + } + + public void testUpdateArray() { + + array1.getArray()[0] = 00.0; + array1.getArray()[1] = 10.0; + array1.getArray()[2] = 20.0; + array1.getArray()[3] = 30.0; + + array2.getArray()[0] = 40.0; + array2.getArray()[1] = 50.0; + + array3.getArray()[0] = 60.0; + array3.getArray()[1] = 70.0; + array3.getArray()[2] = 80.0; + + mapper.updateArray(); + + double[] data = mapper.getInternalDataArray(); + for (int i = 0; i < data.length; ++i) { + assertTrue(Math.abs(data [i] - i * 10.0) < 1.0e-10); + } + + } + + public static Test suite() { + return new TestSuite(MappableArrayTest.class); + } + + public void setUp() { + + reusedArray = new double[2]; + clonedArray = new double[3]; + + array1 = new MappableArray(4); + array2 = new MappableArray(reusedArray, false); + array3 = new MappableArray(clonedArray, true); + + mapper = new ArrayMapper(); + mapper.manageMappable(array1); + mapper.manageMappable(array2); + mapper.manageMappable(array3); + + } + + public void tearDown() { + reusedArray = null; + clonedArray = null; + + array1 = null; + array2 = null; + array3 = null; + + mapper = null; + + } + + private double[] reusedArray; + private double[] clonedArray; + + private MappableArray array1; + private MappableArray array2; + private MappableArray array3; + + private ArrayMapper mapper; + +} diff --git a/src/mantissa/tests-src/org/spaceroots/mantissa/utilities/MappableScalarTest.java b/src/mantissa/tests-src/org/spaceroots/mantissa/utilities/MappableScalarTest.java new file mode 100644 index 000000000..869bcae20 --- /dev/null +++ b/src/mantissa/tests-src/org/spaceroots/mantissa/utilities/MappableScalarTest.java @@ -0,0 +1,96 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.spaceroots.mantissa.utilities; + +import junit.framework.*; + +public class MappableScalarTest + extends TestCase { + + public MappableScalarTest(String name) { + super(name); + } + + public void testDimensionCheck() { + assertTrue(mapper.getInternalDataArray().length == 3); + } + + public void testUpdateObjects() { + + double[] data = new double [mapper.getInternalDataArray().length]; + for (int i = 0; i < data.length; ++i) { + data [i] = i * 0.1; + } + + mapper.updateObjects(data); + + assertTrue(Math.abs(scalar1.getValue() - 0.0) < 1.0e-10); + assertTrue(Math.abs(scalar2.getValue() - 0.1) < 1.0e-10); + assertTrue(Math.abs(scalar3.getValue() - 0.2) < 1.0e-10); + + } + + public void testUpdateArray() { + + scalar1.setValue(00.0); + scalar2.setValue(10.0); + scalar3.setValue(20.0); + + mapper.updateArray(); + + double[] data = mapper.getInternalDataArray(); + for (int i = 0; i < data.length; ++i) { + assertTrue(Math.abs(data [i] - i * 10.0) < 1.0e-10); + } + + } + + public static Test suite() { + return new TestSuite(MappableScalarTest.class); + } + + public void setUp() { + + scalar1 = new MappableScalar(); + scalar2 = new MappableScalar(2); + scalar3 = new MappableScalar(-3); + + mapper = new ArrayMapper(); + mapper.manageMappable(scalar1); + mapper.manageMappable(scalar2); + mapper.manageMappable(scalar3); + + } + + public void tearDown() { + + scalar1 = null; + scalar2 = null; + scalar3 = null; + + mapper = null; + + } + + private MappableScalar scalar1; + private MappableScalar scalar2; + private MappableScalar scalar3; + + private ArrayMapper mapper; + +}