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.
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.
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:
+ *
|
The default values for the algorithm settings are: + *
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.
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 @@ + + +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.
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), wherey = 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
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
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)}.
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
.
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 : + *
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 (fromrange [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 : + *
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 (fromrange [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 anUnsupportedOperationException
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)}.
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
+ * 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 @@ + + +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 thantt0
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:
+ *
|
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. |
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:
+ *
|
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. |
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.
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 thantt0
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.
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 | |
Name | Order |
{@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 | ||
Name | Integration Order | Error Estimation Order |
{@link org.spaceroots.mantissa.ode.HighamHall54Integrator Higham and Hall} | 5 | 4 |
{@link org.spaceroots.mantissa.ode.DormandPrince54Integrator Dormand-Prince 5(4)} | 5 | 4 |
{@link org.spaceroots.mantissa.ode.DormandPrince853Integrator Dormand-Prince 8(5,3)} | 8 | 5 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.
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
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
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
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.
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 matrixB
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.
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.
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:
+ *
|
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; + +}