From 5b0506dcc6d9336fc869443fa70975d27c8f71c1 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Andr=C3=A9?= Date: Wed, 21 Apr 2010 14:36:13 +0200 Subject: [PATCH 1/1] Initial Commit --- AUTHORS | 0 COPYING | 504 +++++++++++++ ChangeLog | 0 INSTALL | 1 + Makefile.am | 34 + NEWS | 0 README | 0 adm_local/Makefile.am | 24 + adm_local/unix/make_common_starter.am | 96 +++ build_configure | 104 +++ clean_configure | 35 + configure.ac | 184 +++++ doc/ComposantAD.pdf | Bin 0 -> 204460 bytes resources/DATASSIMCatalog.xml | 0 resources/Makefile.am | 21 + src/Makefile.am | 21 + src/daComposant/Makefile.am | 24 + src/daComposant/daAlgorithms/3DVAR.py | 216 ++++++ src/daComposant/daAlgorithms/Blue.py | 83 +++ src/daComposant/daAlgorithms/EnsembleBlue.py | 88 +++ src/daComposant/daAlgorithms/Kalman.py | 96 +++ .../daAlgorithms/LinearLeastSquares.py | 62 ++ src/daComposant/daAlgorithms/__init__.py | 19 + src/daComposant/daCore/AssimilationStudy.py | 598 ++++++++++++++++ src/daComposant/daCore/BasicObjects.py | 213 ++++++ src/daComposant/daCore/Logging.py | 162 +++++ src/daComposant/daCore/Persistence.py | 663 ++++++++++++++++++ src/daComposant/daCore/PlatformInfo.py | 255 +++++++ src/daComposant/daCore/version.py | 23 + .../CompareMeanDependantVectors.py | 118 ++++ ...MeanIndependantVectorsDifferentVariance.py | 117 ++++ ...pareMeanIndependantVectorsEqualVariance.py | 116 +++ .../daDiagnostics/CompareVarianceFisher.py | 119 ++++ src/daComposant/daDiagnostics/ComputeBiais.py | 89 +++ .../daDiagnostics/ComputeCostFunction.py | 141 ++++ .../daDiagnostics/ComputeCostFunctionLin.py | 119 ++++ .../daDiagnostics/ComputeVariance.py | 90 +++ .../daDiagnostics/GaussianAdequation.py | 159 +++++ src/daComposant/daDiagnostics/HLinearity.py | 143 ++++ .../daDiagnostics/HomogeneiteKhi2.py | 121 ++++ src/daComposant/daDiagnostics/PlotVector.py | 153 ++++ src/daComposant/daDiagnostics/PlotVectors.py | 157 +++++ src/daComposant/daDiagnostics/RMS.py | 92 +++ src/daComposant/daDiagnostics/ReduceBiais.py | 111 +++ .../daDiagnostics/ReduceVariance.py | 116 +++ .../daDiagnostics/StopReductionVariance.py | 121 ++++ .../daDiagnostics/VarianceOrder.py | 129 ++++ src/daComposant/daDiagnostics/__init__.py | 19 + .../ASTER/Building_AD_from_Aster.xml | 275 ++++++++ .../daExternals/ASTER/Building_H_linear.xml | 335 +++++++++ .../daExternals/YACS/Algorithmes_AD.xml | 252 +++++++ src/daComposant/daExternals/__init__.py | 19 + src/daComposant/daMatrices/__init__.py | 19 + src/daComposant/daNumerics/ComputeFisher.py | 116 +++ src/daComposant/daNumerics/ComputeKhi2.py | 420 +++++++++++ src/daComposant/daNumerics/ComputeStudent.py | 260 +++++++ src/daComposant/daNumerics/__init__.py | 19 + 57 files changed, 7471 insertions(+) create mode 100644 AUTHORS create mode 100644 COPYING create mode 100644 ChangeLog create mode 100644 INSTALL create mode 100644 Makefile.am create mode 100644 NEWS create mode 100644 README create mode 100644 adm_local/Makefile.am create mode 100644 adm_local/unix/make_common_starter.am create mode 100755 build_configure create mode 100755 clean_configure create mode 100644 configure.ac create mode 100644 doc/ComposantAD.pdf create mode 100644 resources/DATASSIMCatalog.xml create mode 100644 resources/Makefile.am create mode 100644 src/Makefile.am create mode 100644 src/daComposant/Makefile.am create mode 100644 src/daComposant/daAlgorithms/3DVAR.py create mode 100644 src/daComposant/daAlgorithms/Blue.py create mode 100644 src/daComposant/daAlgorithms/EnsembleBlue.py create mode 100644 src/daComposant/daAlgorithms/Kalman.py create mode 100644 src/daComposant/daAlgorithms/LinearLeastSquares.py create mode 100644 src/daComposant/daAlgorithms/__init__.py create mode 100644 src/daComposant/daCore/AssimilationStudy.py create mode 100644 src/daComposant/daCore/BasicObjects.py create mode 100644 src/daComposant/daCore/Logging.py create mode 100644 src/daComposant/daCore/Persistence.py create mode 100644 src/daComposant/daCore/PlatformInfo.py create mode 100644 src/daComposant/daCore/version.py create mode 100644 src/daComposant/daDiagnostics/CompareMeanDependantVectors.py create mode 100644 src/daComposant/daDiagnostics/CompareMeanIndependantVectorsDifferentVariance.py create mode 100644 src/daComposant/daDiagnostics/CompareMeanIndependantVectorsEqualVariance.py create mode 100644 src/daComposant/daDiagnostics/CompareVarianceFisher.py create mode 100644 src/daComposant/daDiagnostics/ComputeBiais.py create mode 100644 src/daComposant/daDiagnostics/ComputeCostFunction.py create mode 100644 src/daComposant/daDiagnostics/ComputeCostFunctionLin.py create mode 100644 src/daComposant/daDiagnostics/ComputeVariance.py create mode 100644 src/daComposant/daDiagnostics/GaussianAdequation.py create mode 100644 src/daComposant/daDiagnostics/HLinearity.py create mode 100644 src/daComposant/daDiagnostics/HomogeneiteKhi2.py create mode 100644 src/daComposant/daDiagnostics/PlotVector.py create mode 100644 src/daComposant/daDiagnostics/PlotVectors.py create mode 100644 src/daComposant/daDiagnostics/RMS.py create mode 100644 src/daComposant/daDiagnostics/ReduceBiais.py create mode 100644 src/daComposant/daDiagnostics/ReduceVariance.py create mode 100644 src/daComposant/daDiagnostics/StopReductionVariance.py create mode 100644 src/daComposant/daDiagnostics/VarianceOrder.py create mode 100644 src/daComposant/daDiagnostics/__init__.py create mode 100644 src/daComposant/daExternals/ASTER/Building_AD_from_Aster.xml create mode 100644 src/daComposant/daExternals/ASTER/Building_H_linear.xml create mode 100644 src/daComposant/daExternals/YACS/Algorithmes_AD.xml create mode 100644 src/daComposant/daExternals/__init__.py create mode 100644 src/daComposant/daMatrices/__init__.py create mode 100644 src/daComposant/daNumerics/ComputeFisher.py create mode 100644 src/daComposant/daNumerics/ComputeKhi2.py create mode 100644 src/daComposant/daNumerics/ComputeStudent.py create mode 100644 src/daComposant/daNumerics/__init__.py diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..e69de29 diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..b1e3f5a --- /dev/null +++ b/COPYING @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..e69de29 diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..e490384 --- /dev/null +++ b/INSTALL @@ -0,0 +1 @@ +SALOME2 : PYHELLO module (sample) diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..2035ab4 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,34 @@ +# Copyright (C) 2010 CEA/DEN, EDF R&D, OPEN CASCADE +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +include $(top_srcdir)/adm_local/unix/make_common_starter.am + +ACLOCAL_AMFLAGS = -I adm_local/unix \ + -I ${KERNEL_ROOT_DIR}/salome_adm/unix/config_files \ + -I ${GUI_ROOT_DIR}/adm_local/unix/config_files + +SUBDIRS = adm_local src + +DISTCLEANFILES = a.out aclocal.m4 configure local-install.sh + +EXTRA_DIST += \ + build_configure \ + clean_configure + +dist-hook: + rm -rf `find $(distdir) -name CVS` + diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..e69de29 diff --git a/README b/README new file mode 100644 index 0000000..e69de29 diff --git a/adm_local/Makefile.am b/adm_local/Makefile.am new file mode 100644 index 0000000..f35273f --- /dev/null +++ b/adm_local/Makefile.am @@ -0,0 +1,24 @@ +# Copyright (C) 2007-2008 CEA/DEN, EDF R&D, OPEN CASCADE +# +# Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +# CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +include $(top_srcdir)/adm_local/unix/make_common_starter.am + +SUBDIRS = unix diff --git a/adm_local/unix/make_common_starter.am b/adm_local/unix/make_common_starter.am new file mode 100644 index 0000000..cd85151 --- /dev/null +++ b/adm_local/unix/make_common_starter.am @@ -0,0 +1,96 @@ +# Copyright (C) 2007-2008 CEA/DEN, EDF R&D, OPEN CASCADE +# +# Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +# CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +# ============================================================ +# The following is to avoid PACKAGE_... env variable +# redefinition compilation warnings +# ============================================================ +AM_CXXFLAGS = @KERNEL_CXXFLAGS@ -include SALOMEconfig.h +AM_CPPFLAGS = @KERNEL_CXXFLAGS@ -include SALOMEconfig.h + +# ============================================================ +# This file defines the common definitions used in several +# Makefile. This file must be included, if needed, by the file +# Makefile.am. +# ============================================================ +# Standard directory for installation +# +salomeincludedir = $(includedir)/salome +libdir = $(prefix)/lib@LIB_LOCATION_SUFFIX@/salome +bindir = $(prefix)/bin/salome +salomescriptdir = $(bindir) +salomepythondir = $(pythondir)/salome +salomepyexecdir = $(pyexecdir)/salome + +# Directory for installing idl files +salomeidldir = $(prefix)/idl/salome + +# Directory for installing resource files +salomeresdir = $(prefix)/share/salome/resources/@MODULE_NAME@ + +# Directories for installing admin files +admlocaldir = $(prefix)/adm_local +admlocalunixdir = $(admlocaldir)/unix +admlocalm4dir = $(admlocaldir)/unix/config_files + +# Shared modules installation directory +sharedpkgpythondir = $(salomepythondir)/shared_modules + +# Documentation directory +docdir = $(datadir)/doc/salome + +# common rules + +# meta object implementation files generation (moc) +%_moc.cxx: %.h + $(MOC) $< -o $@ + +# translation (*.qm) files generation (lrelease) +%.qm: %.ts + $(LRELEASE) $< -qm $@ + +# resource files generation (qrcc) +qrc_%.cxx: %.qrc + $(QRCC) $< -o $@ -name $(*F) + +# qt forms files generation (uic) +ui_%.h: %.ui + $(UIC) -o $@ $< + +# extra distributed files +EXTRA_DIST = $(MOC_FILES:%_moc.cxx=%.h) $(QRC_FILES:qrc_%.cxx=%.qrc) \ + $(UIC_FILES:ui_%.h=%.ui) $(nodist_salomeres_DATA:%.qm=%.ts) + +# customize clean operation +mostlyclean-local: + rm -f @builddir@/*_moc.cxx + rm -f @builddir@/*.qm + rm -f @builddir@/ui_*.h + rm -f @builddir@/qrc_*.cxx + +# tests +tests: unittest + +unittest: $(UNIT_TEST_PROG) + @if test "x$(UNIT_TEST_PROG)" != "x"; then \ + $(UNIT_TEST_PROG); \ + fi; diff --git a/build_configure b/build_configure new file mode 100755 index 0000000..299c6f7 --- /dev/null +++ b/build_configure @@ -0,0 +1,104 @@ +#!/bin/bash +# Copyright (C) 2007-2008 CEA/DEN, EDF R&D, OPEN CASCADE +# +# Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +# CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +# Tool for updating list of .in file for the SALOME project +# and regenerating configure script +# Author : +# Modified by : Alexander BORODIN (OCN) - autotools usage +# Date : 10/10/2002 +# +ORIG_DIR=`pwd` +CONF_DIR=`echo $0 | sed -e "s,[^/]*$,,;s,/$,,;s,^$,.,"` + +######################################################################## +# Test if the KERNEL_ROOT_DIR is set correctly + +if test ! -d "${KERNEL_ROOT_DIR}"; then + echo "failed : KERNEL_ROOT_DIR variable is not correct !" + exit +fi + + +######################################################################## +# Test if the GUI_ROOT_DIR is set correctly + +if test ! -d "${GUI_ROOT_DIR}"; then + echo "failed : GUI_ROOT_DIR variable is not correct !" + exit +fi + +cd ${CONF_DIR} +ABS_CONF_DIR=`pwd` + +######################################################################## + +# ____________________________________________________________________ +# aclocal creates the aclocal.m4 file from the standard macro and the +# custom macro embedded in the directory adm_local/unix/config_files +# and KERNEL config_files directory. +# output: +# aclocal.m4 +# autom4te.cache (directory) +echo "======================================================= aclocal" + +aclocal -I adm_local/unix/config_files \ + -I ${KERNEL_ROOT_DIR}/salome_adm/unix/config_files \ + -I ${GUI_ROOT_DIR}/adm_local/unix/config_files || exit 1 + +# ____________________________________________________________________ +# libtoolize creates some configuration files (ltmain.sh, +# config.guess and config.sub). It only depends on the libtool +# version. The files are created in the directory specified with the +# AC_CONFIG_AUX_DIR() tag (see configure.ac). +# output: +# adm_local/unix/config_files/config.guess +# adm_local/unix/config_files/config.sub +# adm_local/unix/config_files/ltmain.sh +echo "==================================================== libtoolize" + +libtoolize --force --copy --automake || exit 1 + +# ____________________________________________________________________ +# autoconf creates the configure script from the file configure.ac (or +# configure.in if configure.ac doesn't exist) +# output: +# configure +echo "====================================================== autoconf" + +autoconf + +# ____________________________________________________________________ +# automake creates some scripts used in building process +# (install-sh, missing, ...). It only depends on the automake +# version. The files are created in the directory specified with the +# AC_CONFIG_AUX_DIR() tag (see configure.ac). This step also +# creates the Makefile.in files from the Makefile.am files. +# output: +# adm_local/unix/config_files/compile +# adm_local/unix/config_files/depcomp +# adm_local/unix/config_files/install-sh +# adm_local/unix/config_files/missing +# adm_local/unix/config_files/py-compile +# Makefile.in (from Makefile.am) +echo "====================================================== automake" + +automake --copy --gnu --add-missing diff --git a/clean_configure b/clean_configure new file mode 100755 index 0000000..f57f7b3 --- /dev/null +++ b/clean_configure @@ -0,0 +1,35 @@ +#!/bin/sh +# Copyright (C) 2007-2008 CEA/DEN, EDF R&D, OPEN CASCADE +# +# Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +# CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +rm -rf autom4te.cache aclocal.m4 configure make_config +find . -name "*~" -print -exec rm {} \; +find . -name "*.pyc" -print -exec rm {} \; +#exit +# ==================== ON SORT AVANT + +find bin -name Makefile.in | xargs rm -f +find doc -name Makefile.in | xargs rm -f +find idl -name Makefile.in | xargs rm -f +find resources -name Makefile.in | xargs rm -f +find salome_adm -name Makefile.in | xargs rm -f +find src -name Makefile.in | xargs rm -f +rm -f Makefile.in diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..fdfb80f --- /dev/null +++ b/configure.ac @@ -0,0 +1,184 @@ +# Copyright (C) 2010 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +AC_INIT([DATASSIM module], [0.1], [andre.ribes@edf.fr], [SalomeDATASSIM]) +AC_CONFIG_AUX_DIR(adm_local/unix/config_files) +AC_CANONICAL_HOST +AC_CANONICAL_TARGET +AM_INIT_AUTOMAKE([-Wno-portability]) + +XVERSION=`echo $VERSION | awk -F. '{printf("0x%02x%02x%02x",$1,$2,$3)}'` +AC_SUBST(XVERSION) + +# set up MODULE_NAME variable for dynamic construction of directories (resources, etc.) +MODULE_NAME=datassim +AC_SUBST(MODULE_NAME) + +dnl +dnl Initialize source and build root directories +dnl + +ROOT_BUILDDIR=`pwd` +ROOT_SRCDIR=`echo $0 | sed -e "s,[[^/]]*$,,;s,/$,,;s,^$,.,"` +cd $ROOT_SRCDIR +ROOT_SRCDIR=`pwd` +cd $ROOT_BUILDDIR + +AC_SUBST(ROOT_SRCDIR) +AC_SUBST(ROOT_BUILDDIR) + +echo +echo Source root directory : $ROOT_SRCDIR +echo Build root directory : $ROOT_BUILDDIR +echo +echo + +AC_CHECK_PROG(SHELL,sh) +AC_SUBST(SHELL) + +if test -z "$AR"; then + AC_CHECK_PROGS(AR,ar xar,:,$PATH) +fi +AC_SUBST(AR) + +dnl Export the AR macro so that it will be placed in the libtool file +dnl correctly. +export AR + +echo +echo --------------------------------------------- +echo testing make +echo --------------------------------------------- +echo + +AC_PROG_MAKE_SET +AC_PROG_INSTALL +AC_LOCAL_INSTALL +dnl +dnl libtool macro check for CC, LD, NM, LN_S, RANLIB, STRIP + for shared libraries + +AC_ENABLE_DEBUG(yes) +AC_DISABLE_PRODUCTION + +echo --------------------------------------------- +echo testing libtool +echo --------------------------------------------- + +dnl first, we set static to no! +dnl if we want it, use --enable-static +AC_ENABLE_STATIC(no) + +AC_LIBTOOL_DLOPEN +AC_PROG_LIBTOOL + +dnl Fix up the INSTALL macro if it s a relative path. We want the +dnl full-path to the binary instead. +case "$INSTALL" in + *install-sh*) + INSTALL='\${KERNEL_ROOT_DIR}'/adm_local/unix/config_files/install-sh + ;; +esac + +echo +echo --------------------------------------------- +echo testing python +echo --------------------------------------------- +echo + +CHECK_PYTHON + +AM_PATH_PYTHON(2.4) + +echo +echo --------------------------------------------- +echo testing QT +echo --------------------------------------------- +echo + +CHECK_QT + +echo +echo --------------------------------------------- +echo Testing html generators +echo --------------------------------------------- +echo + +CHECK_HTML_GENERATORS + +echo +echo --------------------------------------------- +echo Testing Kernel +echo --------------------------------------------- +echo + +CHECK_KERNEL + +echo +echo --------------------------------------------- +echo Testing GUI +echo --------------------------------------------- +echo + +CHECK_SALOME_GUI + +echo +echo --------------------------------------------- +echo Summary +echo --------------------------------------------- +echo + +echo Configure +variables="python_ok qt_ok doxygen_ok Kernel_ok" + +for var in $variables +do + printf " %10s : " `echo \$var | sed -e "s,_ok,,"` + eval echo \$$var +done + +dnl We don t need to say when we re entering directories if we re using +dnl GNU make becuase make does it for us. +if test "X$GMAKE" = "Xyes"; then + AC_SUBST(SETX) SETX=":" +else + AC_SUBST(SETX) SETX="set -x" +fi +echo +echo --------------------------------------------- +echo generating Makefiles and configure files +echo --------------------------------------------- +echo + +AC_OUTPUT_COMMANDS([ \ + chmod +x ./bin/*; \ +]) + +# This list is initiated using autoscan and must be updated manually +# when adding a new file .in to manage. When you execute +# autoscan, the Makefile list is generated in the output file configure.scan. +# This could be helpfull to update de configuration. +AC_OUTPUT([ \ + adm_local/Makefile \ + adm_local/unix/Makefile \ + src/Makefile \ + src/DATASSIM/Makefile \ + src/DATASSIMGUI/Makefile \ + resources/Makefile \ + Makefile \ +]) diff --git a/doc/ComposantAD.pdf b/doc/ComposantAD.pdf new file mode 100644 index 0000000000000000000000000000000000000000..19a5fe8f756412676282380e1c96232c0ed146bd GIT binary patch literal 204460 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 PxguHF}2TMeJ>Pao@7 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
DjzbtSl+3*RFkr-TDu8% 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 literal 0 HcmV?d00001 diff --git a/resources/DATASSIMCatalog.xml b/resources/DATASSIMCatalog.xml new file mode 100644 index 0000000..e69de29 diff --git a/resources/Makefile.am b/resources/Makefile.am new file mode 100644 index 0000000..987593a --- /dev/null +++ b/resources/Makefile.am @@ -0,0 +1,21 @@ +# Copyright (C) 2010 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# + +include $(top_srcdir)/adm_local/unix/make_common_starter.am + +dist_salomeres_DATA = DATASSIMCatalog.xml diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..77d5101 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,21 @@ +# Copyright (C) 2010 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com + +include $(top_srcdir)/adm_local/unix/make_common_starter.am + +SUBDIRS = daComposant diff --git a/src/daComposant/Makefile.am b/src/daComposant/Makefile.am new file mode 100644 index 0000000..522eda7 --- /dev/null +++ b/src/daComposant/Makefile.am @@ -0,0 +1,24 @@ +# Copyright (C) 2010 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +include $(top_srcdir)/adm_local/unix/make_common_starter.am + +# Scripts to be installed +dist_salomescript_SCRIPTS = daCore + diff --git a/src/daComposant/daAlgorithms/3DVAR.py b/src/daComposant/daAlgorithms/3DVAR.py new file mode 100644 index 0000000..d1ad427 --- /dev/null +++ b/src/daComposant/daAlgorithms/3DVAR.py @@ -0,0 +1,216 @@ +#-*-coding:iso-8859-1-*- +# +# Copyright (C) 2008-2009 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +__doc__ = """ + Algorithme variationnel statique (3D-VAR) +""" +__author__ = "Jean-Philippe ARGAUD - Mars 2009" + +import sys ; sys.path.insert(0, "../daCore") +import logging +import Persistence +from BasicObjects import Algorithm +import PlatformInfo ; m = PlatformInfo.SystemUsage() + +import numpy +import scipy.optimize + +if logging.getLogger().level < 30: + iprint = 1 + message = scipy.optimize.tnc.MSG_ALL + disp = 1 +else: + iprint = -1 + message = scipy.optimize.tnc.MSG_NONE + disp = 0 + +# ============================================================================== +class ElementaryAlgorithm(Algorithm): + def __init__(self): + Algorithm.__init__(self) + self._name = "3DVAR" + logging.debug("%s Initialisation"%self._name) + + def run(self, Xb=None, Y=None, H=None, M=None, R=None, B=None, Q=None, Par=None): + """ + Calcul de l'estimateur 3D-VAR + """ + logging.debug("%s Lancement"%self._name) + logging.debug("%s Taille mémoire utilisée de %.1f Mo"%(self._name, m.getUsedMemory("Mo"))) + # + Hm = H["Direct"].appliedTo + Ht = H["Adjoint"].appliedInXTo + # + # Utilisation éventuelle d'un vecteur H(Xb) précalculé + # ---------------------------------------------------- + if H["AppliedToX"] is not None and H["AppliedToX"].has_key("HXb"): + logging.debug("%s Utilisation de HXb"%self._name) + HXb = H["AppliedToX"]["HXb"] + else: + logging.debug("%s Calcul de Hm(Xb)"%self._name) + HXb = Hm( Xb ) + # + # Calcul du préconditionnement + # ---------------------------- + # Bdemi = numpy.linalg.cholesky(B) + # + # Calcul de l'innovation + # ---------------------- + d = Y - HXb + logging.debug("%s Innovation d = %s"%(self._name, d)) + # + # Précalcul des inversion appellée dans les fonction-coût et gradient + # ------------------------------------------------------------------- + BI = B.I + RI = R.I + # + # Définition de la fonction-coût + # ------------------------------ + def CostFunction(x): + _X = numpy.asmatrix(x).flatten().T + logging.info("%s CostFunction X = %s"%(self._name, numpy.asmatrix( _X ).flatten())) + _HX = Hm( _X ) + _HX = numpy.asmatrix(_HX).flatten().T + Jb = 0.5 * (_X - Xb).T * BI * (_X - Xb) + Jo = 0.5 * (Y - _HX).T * RI * (Y - _HX) + J = float( Jb ) + float( Jo ) + logging.info("%s CostFunction Jb = %s"%(self._name, Jb)) + logging.info("%s CostFunction Jo = %s"%(self._name, Jo)) + logging.info("%s CostFunction J = %s"%(self._name, J)) + self.StoredVariables["CostFunctionJb"].store( Jb ) + self.StoredVariables["CostFunctionJo"].store( Jo ) + self.StoredVariables["CostFunctionJ" ].store( J ) + return float( J ) + # + def GradientOfCostFunction(x): + _X = numpy.asmatrix(x).flatten().T + logging.info("%s GradientOfCostFunction X = %s"%(self._name, numpy.asmatrix( _X ).flatten())) + _HX = Hm( _X ) + _HX = numpy.asmatrix(_HX).flatten().T + GradJb = BI * (_X - Xb) + GradJo = - Ht( (_X, RI * (Y - _HX)) ) + GradJ = numpy.asmatrix( GradJb ).flatten().T + numpy.asmatrix( GradJo ).flatten().T + logging.debug("%s GradientOfCostFunction GradJb = %s"%(self._name, numpy.asmatrix( GradJb ).flatten())) + logging.debug("%s GradientOfCostFunction GradJo = %s"%(self._name, numpy.asmatrix( GradJo ).flatten())) + logging.debug("%s GradientOfCostFunction GradJ = %s"%(self._name, numpy.asmatrix( GradJ ).flatten())) + # self.StoredVariables["GradientOfCostFunctionJb"].store( Jb ) + # self.StoredVariables["GradientOfCostFunctionJo"].store( Jo ) + # self.StoredVariables["GradientOfCostFunctionJ" ].store( J ) + return GradJ.A1 + # + # Point de démarrage de l'optimisation : Xini = Xb + # ------------------------------------ + if type(Xb) is type(numpy.matrix([])): + Xini = Xb.A1.tolist() + else: + Xini = list(Xb) + logging.debug("%s Point de démarrage Xini = %s"%(self._name, Xini)) + # + # Paramètres de pilotage + # ---------------------- + if Par.has_key("Bounds") and (type(Par["Bounds"]) is type([]) or type(Par["Bounds"]) is type(())) and (len(Par["Bounds"]) > 0): + Bounds = Par["Bounds"] + else: + Bounds = None + MinimizerList = ["LBFGSB","TNC", "CG", "BFGS"] + if Par.has_key("Minimizer") and (Par["Minimizer"] in MinimizerList): + Minimizer = str( Par["Minimizer"] ) + else: + Minimizer = "LBFGSB" + logging.debug("%s Minimiseur utilisé = %s"%(self._name, Minimizer)) + if Par.has_key("MaximumNumberOfSteps") and (Par["MaximumNumberOfSteps"] > -1): + maxiter = int( Par["MaximumNumberOfSteps"] ) + else: + maxiter = 15000 + logging.debug("%s Nombre maximal de pas d'optimisation = %s"%(self._name, maxiter)) + # + # Minimisation de la fonctionnelle + # -------------------------------- + if Minimizer == "LBFGSB": + Minimum, J_optimal, Informations = scipy.optimize.fmin_l_bfgs_b( + func = CostFunction, + x0 = Xini, + fprime = GradientOfCostFunction, + args = (), + bounds = Bounds, + maxfun = maxiter, + iprint = iprint, + ) + logging.debug("%s %s Minimum = %s"%(self._name, Minimizer, Minimum)) + logging.debug("%s %s Nb of F = %s"%(self._name, Minimizer, Informations['funcalls'])) + logging.debug("%s %s RetCode = %s"%(self._name, Minimizer, Informations['warnflag'])) + elif Minimizer == "TNC": + Minimum, nfeval, rc = scipy.optimize.fmin_tnc( + func = CostFunction, + x0 = Xini, + fprime = GradientOfCostFunction, + args = (), + bounds = Bounds, + maxfun = maxiter, + messages = message, + ) + logging.debug("%s %s Minimum = %s"%(self._name, Minimizer, Minimum)) + logging.debug("%s %s Nb of F = %s"%(self._name, Minimizer, nfeval)) + logging.debug("%s %s RetCode = %s"%(self._name, Minimizer, rc)) + elif Minimizer == "CG": + Minimum, fopt, nfeval, grad_calls, rc = scipy.optimize.fmin_cg( + f = CostFunction, + x0 = Xini, + fprime = GradientOfCostFunction, + args = (), + maxiter = maxiter, + disp = disp, + full_output = True, + ) + logging.debug("%s %s Minimum = %s"%(self._name, Minimizer, Minimum)) + logging.debug("%s %s Nb of F = %s"%(self._name, Minimizer, nfeval)) + logging.debug("%s %s RetCode = %s"%(self._name, Minimizer, rc)) + elif Minimizer == "BFGS": + Minimum, fopt, gopt, Hopt, nfeval, grad_calls, rc = scipy.optimize.fmin_bfgs( + f = CostFunction, + x0 = Xini, + fprime = GradientOfCostFunction, + args = (), + maxiter = maxiter, + disp = disp, + full_output = True, + ) + logging.debug("%s %s Minimum = %s"%(self._name, Minimizer, Minimum)) + logging.debug("%s %s Nb of F = %s"%(self._name, Minimizer, nfeval)) + logging.debug("%s %s RetCode = %s"%(self._name, Minimizer, rc)) + else: + raise ValueError("Error in Minimizer name: %s"%Minimizer) + # + # Calcul de l'analyse + # -------------------- + Xa = numpy.asmatrix(Minimum).T + logging.debug("%s Analyse Xa = %s"%(self._name, Xa)) + # + self.StoredVariables["Analysis"].store( Xa.A1 ) + self.StoredVariables["Innovation"].store( d.A1 ) + # + logging.debug("%s Taille mémoire utilisée de %.1f Mo"%(self._name, m.getUsedMemory("MB"))) + logging.debug("%s Terminé"%self._name) + # + return 0 + +# ============================================================================== +if __name__ == "__main__": + print '\n AUTODIAGNOSTIC \n' diff --git a/src/daComposant/daAlgorithms/Blue.py b/src/daComposant/daAlgorithms/Blue.py new file mode 100644 index 0000000..3e5704d --- /dev/null +++ b/src/daComposant/daAlgorithms/Blue.py @@ -0,0 +1,83 @@ +#-*-coding:iso-8859-1-*- +# +# Copyright (C) 2008-2009 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +__doc__ = """ + Algorithme de Kalman simple (BLUE) +""" +__author__ = "Jean-Philippe ARGAUD - Mars 2008" + +import sys ; sys.path.insert(0, "../daCore") +import logging +import Persistence +from BasicObjects import Algorithm +import PlatformInfo ; m = PlatformInfo.SystemUsage() + +# ============================================================================== +class ElementaryAlgorithm(Algorithm): + def __init__(self): + Algorithm.__init__(self) + self._name = "BLUE" + logging.debug("%s Initialisation"%self._name) + + def run(self, Xb=None, Y=None, H=None, M=None, R=None, B=None, Q=None, Par=None): + """ + Calcul de l'estimateur BLUE (ou Kalman simple, ou Interpolation Optimale) + """ + logging.debug("%s Lancement"%self._name) + logging.debug("%s Taille mémoire utilisée de %.1f Mo"%(self._name, m.getUsedMemory("Mo"))) + # + Hm = H["Direct"].asMatrix() + Ht = H["Adjoint"].asMatrix() + # + # Utilisation éventuelle d'un vecteur H(Xb) précalculé + # ---------------------------------------------------- + if H["AppliedToX"] is not None and H["AppliedToX"].has_key("HXb"): + logging.debug("%s Utilisation de HXb"%self._name) + HXb = H["AppliedToX"]["HXb"] + else: + logging.debug("%s Calcul de Hm * Xb"%self._name) + HXb = Hm * Xb + + # Calcul de la matrice de gain dans l'espace le plus petit + if Y.size <= Xb.size: + logging.debug("%s Calcul de K dans l'espace des observations"%self._name) + K = B * Ht * (Hm * B * Ht + R).I + else: + logging.debug("%s Calcul de K dans l'espace d'ébauche"%self._name) + K = (Ht * R.I * Hm + B.I).I * Ht * R.I + # + # Calcul de l'innovation et de l'analyse + # -------------------------------------- + d = Y - HXb + logging.debug("%s Innovation d = %s"%(self._name, d)) + Xa = Xb + K*d + logging.debug("%s Analyse Xa = %s"%(self._name, Xa)) + # + self.StoredVariables["Analysis"].store( Xa.A1 ) + self.StoredVariables["Innovation"].store( d.A1 ) + # + logging.debug("%s Taille mémoire utilisée de %.1f Mo"%(self._name, m.getUsedMemory("MB"))) + logging.debug("%s Terminé"%self._name) + # + return 0 + +# ============================================================================== +if __name__ == "__main__": + print '\n AUTODIAGNOSTIC \n' diff --git a/src/daComposant/daAlgorithms/EnsembleBlue.py b/src/daComposant/daAlgorithms/EnsembleBlue.py new file mode 100644 index 0000000..287e81a --- /dev/null +++ b/src/daComposant/daAlgorithms/EnsembleBlue.py @@ -0,0 +1,88 @@ +#-*-coding:iso-8859-1-*- +# +# Copyright (C) 2008-2009 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +__doc__ = """ + Algorithme de methode d'ensemble simple +""" +__author__ = "Sebastien MASSART, Jean-Philippe ARGAUD - Novembre 2008" + +import sys ; sys.path.insert(0, "../daCore") +import logging +import numpy +import Persistence +from BasicObjects import Algorithm +import PlatformInfo ; m = PlatformInfo.SystemUsage() + +# ============================================================================== +class ElementaryAlgorithm(Algorithm): + def __init__(self): + Algorithm.__init__(self) + self._name = "ENSEMBLEBLUE" + logging.debug("%s Initialisation"%self._name) + + def run(self, Xb=None, Y=None, H=None, M=None, R=None, B=None, Q=None, Par=None ): + """ + Calcul d'une estimation BLUE d'ensemble : + - génération d'un ensemble d'observations, de même taille que le + nombre d'ébauches + - calcul de l'estimateur BLUE pour chaque membre de l'ensemble + """ + logging.debug("%s Lancement"%self._name) + logging.debug("%s Taille mémoire utilisée de %.1f Mo"%(self._name, m.getUsedMemory("Mo"))) + # + # Nombre d'ensemble pour l'ébauche + # -------------------------------- + nb_ens = Xb.stepnumber() + # + # Construction de l'ensemble des observations, par génération a partir + # de la diagonale de R + # -------------------------------------------------------------------- + DiagonaleR = numpy.diag(R) + EnsembleY = numpy.zeros([len(Y),nb_ens]) + for npar in range(len(DiagonaleR)) : + bruit = numpy.random.normal(0,DiagonaleR[npar],nb_ens) + EnsembleY[npar,:] = Y[npar] + bruit + EnsembleY = numpy.matrix(EnsembleY) + # + # Initialisation des opérateurs d'observation et de la matrice gain + # ----------------------------------------------------------------- + Hm = H["Direct"].asMatrix() + Ht = H["Adjoint"].asMatrix() + + K = B * Ht * (Hm * B * Ht + R).I + + # Calcul du BLUE pour chaque membre de l'ensemble + # ----------------------------------------------- + for iens in range(nb_ens): + d = EnsembleY[:,iens] - Hm * Xb.valueserie(iens) + Xa = Xb.valueserie(iens) + K*d + + self.StoredVariables["Analysis"].store( Xa.A1 ) + self.StoredVariables["Innovation"].store( d.A1 ) + # + logging.debug("%s Taille mémoire utilisée de %.1f Mo"%(self._name, m.getUsedMemory("Mo"))) + logging.debug("%s Terminé"%self._name) + return 0 + +# ============================================================================== +if __name__ == "__main__": + print '\n AUTODIAGNOSTIC \n' + + diff --git a/src/daComposant/daAlgorithms/Kalman.py b/src/daComposant/daAlgorithms/Kalman.py new file mode 100644 index 0000000..d4c817f --- /dev/null +++ b/src/daComposant/daAlgorithms/Kalman.py @@ -0,0 +1,96 @@ +#-*-coding:iso-8859-1-*- +# +# Copyright (C) 2008-2009 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +__doc__ = """ + Algorithme de Kalman pour un système discret + + Remarque : les observations sont exploitées à partir du pas de temps 1, et + sont utilisées dans Yo comme rangées selon ces indices. Donc le pas 0 n'est + pas utilisé puisque la première étape de Kalman passe de 0 à 1 avec + l'observation du pas 1. +""" +__author__ = "Jean-Philippe ARGAUD - Septembre 2008" + +import sys ; sys.path.insert(0, "../daCore") +import logging +import Persistence +from BasicObjects import Algorithm +import PlatformInfo ; m = PlatformInfo.SystemUsage() + +# ============================================================================== +class ElementaryAlgorithm(Algorithm): + def __init__(self): + Algorithm.__init__(self) + self._name = "KALMAN" + logging.debug("%s Initialisation"%self._name) + + def run(self, Xb=None, Y=None, H=None, M=None, R=None, B=None, Q=None, Par=None): + """ + Calcul de l'estimateur de Kalman + """ + logging.debug("%s Lancement"%self._name) + logging.debug("%s Taille mémoire utilisée de %.1f Mo"%(self._name, m.getUsedMemory("Mo"))) + # + # Opérateur d'observation + # ----------------------- + Hm = H["Direct"].asMatrix() + Ht = H["Adjoint"].asMatrix() + # + # Opérateur d'évolution + # --------------------- + Mm = M["Direct"].asMatrix() + Mt = M["Adjoint"].asMatrix() + # + duration = Y.stepnumber() + # + # Initialisation + # -------------- + Xn = Xb + Pn = B + self.StoredVariables["Analysis"].store( Xn.A1 ) + self.StoredVariables["CovarianceAPosteriori"].store( Pn ) + # + for step in range(duration-1): + logging.debug("%s Etape de Kalman %i (i.e. %i->%i) sur un total de %i"%(self._name, step+1, step,step+1, duration-1)) + # + # Etape de prédiction + # ------------------- + Xn_predicted = Mm * Xn + Pn_predicted = Mm * Pn * Mt + Q + # + # Etape de correction + # ------------------- + d = Y.valueserie(step+1) - Hm * Xn_predicted + K = Pn_predicted * Ht * (Hm * Pn_predicted * Ht + R).I + Xn = Xn_predicted + K * d + Pn = Pn_predicted - K * Hm * Pn_predicted + # + self.StoredVariables["Analysis"].store( Xn.A1 ) + self.StoredVariables["CovarianceAPosteriori"].store( Pn ) + self.StoredVariables["Innovation"].store( d.A1 ) + # + logging.debug("%s Taille mémoire utilisée de %.1f Mo"%(self._name, m.getUsedMemory("Mo"))) + logging.debug("%s Terminé"%self._name) + # + return 0 + +# ============================================================================== +if __name__ == "__main__": + print '\n AUTODIAGNOSTIC \n' diff --git a/src/daComposant/daAlgorithms/LinearLeastSquares.py b/src/daComposant/daAlgorithms/LinearLeastSquares.py new file mode 100644 index 0000000..855d8a1 --- /dev/null +++ b/src/daComposant/daAlgorithms/LinearLeastSquares.py @@ -0,0 +1,62 @@ +#-*-coding:iso-8859-1-*- +# +# Copyright (C) 2008-2009 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +__doc__ = """ + Algorithme de moindre carres pondérés (analyse sans ebauche) +""" +__author__ = "Sophie RICCI, Jean-Philippe ARGAUD - Septembre 2008" + +import sys ; sys.path.insert(0, "../daCore") +import logging +import Persistence +from BasicObjects import Algorithm +import PlatformInfo ; m = PlatformInfo.SystemUsage() + +# ============================================================================== +class ElementaryAlgorithm(Algorithm): + def __init__(self): + Algorithm.__init__(self) + self._name = "LINEARLEASTSQUARES" + + def run(self, Xb=None, Y=None, H=None, M=None, R=None, B=None, Q=None, Par=None): + """ + Calcul de l'estimateur au sens des moindres carres sans ebauche + """ + logging.debug("%s Lancement"%self._name) + logging.debug("%s Taille mémoire utilisée de %.1f Mo"%(self._name, m.getUsedMemory("Mo"))) + # + Hm = H["Direct"].asMatrix() + Ht = H["Adjoint"].asMatrix() + # + K = (Ht * R.I * Hm ).I * Ht * R.I + Xa = K * Y + # + self.StoredVariables["Analysis"].store( Xa.A1 ) + # + logging.debug("%s Taille mémoire utilisée de %.1f Mo"%(self._name, m.getUsedMemory("Mo"))) + logging.debug("%s Terminé"%self._name) + # + return 0 + +# ============================================================================== +if __name__ == "__main__": + print '\n AUTODIAGNOSTIC \n' + + diff --git a/src/daComposant/daAlgorithms/__init__.py b/src/daComposant/daAlgorithms/__init__.py new file mode 100644 index 0000000..6bcb582 --- /dev/null +++ b/src/daComposant/daAlgorithms/__init__.py @@ -0,0 +1,19 @@ +# +# Copyright (C) 2008-2009 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# diff --git a/src/daComposant/daCore/AssimilationStudy.py b/src/daComposant/daCore/AssimilationStudy.py new file mode 100644 index 0000000..83b4813 --- /dev/null +++ b/src/daComposant/daCore/AssimilationStudy.py @@ -0,0 +1,598 @@ +#-*-coding:iso-8859-1-*- +# +# Copyright (C) 2008-2009 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +__doc__ = """ + Définit les outils généraux élémentaires. + + Ce module est destiné à etre appelée par AssimilationStudy pour constituer + les objets élémentaires de l'algorithme. +""" +__author__ = "Jean-Philippe ARGAUD - Mars 2008" + +import os, sys +import numpy +import Logging ; Logging.Logging() # A importer en premier +import Persistence +from BasicObjects import Operator + +# ============================================================================== +class AssimilationStudy: + """ + Cette classe sert d'interface pour l'utilisation de l'assimilation de + données. Elle contient les méthodes ou accesseurs nécessaires à la + construction d'un calcul d'assimilation. + """ + def __init__(self, name=""): + """ + Prévoit de conserver l'ensemble des variables nécssaires à un algorithme + élémentaire. Ces variables sont ensuite disponibles pour implémenter un + algorithme élémentaire particulier. + + Background............: vecteur Xb + Observation...........: vecteur Y (potentiellement temporel) + d'observations + State.................: vecteur d'état dont une partie est le vecteur de + contrôle. Cette information n'est utile que si l'on veut faire des + calculs sur l'état complet, mais elle n'est pas indispensable pour + l'assimilation. + Control...............: vecteur X contenant toutes les variables de + contrôle, i.e. les paramètres ou l'état dont on veut estimer la + valeur pour obtenir les observations + ObservationOperator...: opérateur d'observation H + + Les observations présentent une erreur dont la matrice de covariance est + R. L'ébauche du vecteur de contrôle présente une erreur dont la matrice + de covariance est B. + """ + self.__name = str(name) + self.__Xb = None + self.__Y = None + self.__B = None + self.__R = None + self.__Q = None + self.__H = {} + self.__M = {} + # + self.__X = Persistence.OneVector() + self.__Parameters = {} + self.__StoredDiagnostics = {} + # + # Variables temporaires + self.__algorithm = {} + self.__algorithmFile = None + self.__algorithmName = None + self.__diagnosticFile = None + # + # Récupère le chemin du répertoire parent et l'ajoute au path + # (Cela complète l'action de la classe PathManagement dans PlatformInfo, + # qui est activée dans Persistence) + self.__parent = os.path.abspath(os.path.join(os.path.dirname(__file__),"..")) + sys.path.insert(0, self.__parent) + sys.path = list(set(sys.path)) # Conserve en unique exemplaire chaque chemin + + # --------------------------------------------------------- + def setBackground(self, + asVector = None, + asPersistentVector = None, + Scheduler = None, + ): + """ + Permet de définir l'estimation a priori : + - asVector : entrée des données, comme un vecteur compatible avec le + constructeur de numpy.matrix + - asPersistentVector : entrée des données, comme un vecteur de type + persistent contruit avec la classe ad-hoc "Persistence" + - Scheduler est le contrôle temporel des données + """ + if asVector is not None: + if type( asVector ) is type( numpy.matrix([]) ): + self.__Xb = numpy.matrix( asVector.A1, numpy.float ).T + else: + self.__Xb = numpy.matrix( asVector, numpy.float ).T + elif asPersistentVector is not None: + self.__Xb = asPersistentVector + else: + raise ValueError("Error: improperly defined background") + return 0 + + def setBackgroundError(self, asCovariance=None): + """ + Permet de définir la covariance des erreurs d'ébauche : + - asCovariance : entrée des données, comme une matrice compatible avec + le constructeur de numpy.matrix + """ + self.__B = numpy.matrix( asCovariance, numpy.float ) + return 0 + + # ----------------------------------------------------------- + def setObservation(self, + asVector = None, + asPersistentVector = None, + Scheduler = None, + ): + """ + Permet de définir les observations : + - asVector : entrée des données, comme un vecteur compatible avec le + constructeur de numpy.matrix + - asPersistentVector : entrée des données, comme un vecteur de type + persistent contruit avec la classe ad-hoc "Persistence" + - Scheduler est le contrôle temporel des données disponibles + """ + if asVector is not None: + if type( asVector ) is type( numpy.matrix([]) ): + self.__Y = numpy.matrix( asVector.A1, numpy.float ).T + else: + self.__Y = numpy.matrix( asVector, numpy.float ).T + elif asPersistentVector is not None: + self.__Y = asPersistentVector + else: + raise ValueError("Error: improperly defined observations") + return 0 + + def setObservationError(self, asCovariance=None): + """ + Permet de définir la covariance des erreurs d'observations : + - asCovariance : entrée des données, comme une matrice compatible avec + le constructeur de numpy.matrix + """ + self.__R = numpy.matrix( asCovariance, numpy.float ) + return 0 + + def setObservationOperator(self, + asFunction = {"Direct":None, "Tangent":None, "Adjoint":None}, + asMatrix = None, + appliedToX = None, + ): + """ + Permet de définir un opérateur d'observation H. L'ordre de priorité des + définitions et leur sens sont les suivants : + - si asFunction["Tangent"] et asFunction["Adjoint"] ne sont pas None + alors on définit l'opérateur à l'aide de fonctions. Si la fonction + "Direct" n'est pas définie, on prend la fonction "Tangent". + - si les fonctions ne sont pas disponibles et si asMatrix n'est pas + None, alors on définit l'opérateur "Direct" et "Tangent" à l'aide de + la matrice, et l'opérateur "Adjoint" à l'aide de la transposée. La + matrice fournie doit être sous une forme compatible avec le + constructeur de numpy.matrix. + - si l'argument "appliedToX" n'est pas None, alors on définit, pour des + X divers, l'opérateur par sa valeur appliquée à cet X particulier, + sous la forme d'un dictionnaire appliedToX[NAME] avec NAME un nom. + L'opérateur doit néanmoins déjà avoir été défini comme d'habitude. + """ + if (type(asFunction) is type({})) and (asFunction["Tangent"] is not None) and (asFunction["Adjoint"] is not None): + if not asFunction.has_key("Direct") or (asFunction["Direct"] is None): + self.__H["Direct"] = Operator( fromMethod = asFunction["Tangent"] ) + else: + self.__H["Direct"] = Operator( fromMethod = asFunction["Direct"] ) + self.__H["Tangent"] = Operator( fromMethod = asFunction["Tangent"] ) + self.__H["Adjoint"] = Operator( fromMethod = asFunction["Adjoint"] ) + elif asMatrix is not None: + mat = numpy.matrix( asMatrix, numpy.float ) + self.__H["Direct"] = Operator( fromMatrix = mat ) + self.__H["Tangent"] = Operator( fromMatrix = mat ) + self.__H["Adjoint"] = Operator( fromMatrix = mat.T ) + else: + raise ValueError("Error: improperly defined observation operator") + # + if appliedToX is not None: + self.__H["AppliedToX"] = {} + if type(appliedToX) is not dict: + raise ValueError("Error: observation operator defined by \"appliedToX\" need a dictionary as argument.") + for key in appliedToX.keys(): + if type( appliedToX[key] ) is type( numpy.matrix([]) ): + # Pour le cas où l'on a une vraie matrice + self.__H["AppliedToX"][key] = numpy.matrix( appliedToX[key].A1, numpy.float ).T + elif type( appliedToX[key] ) is type( numpy.array([]) ) and len(appliedToX[key].shape) > 1: + # Pour le cas où l'on a un vecteur représenté en array avec 2 dimensions + self.__H["AppliedToX"][key] = numpy.matrix( appliedToX[key].reshape(len(appliedToX[key]),), numpy.float ).T + else: + self.__H["AppliedToX"][key] = numpy.matrix( appliedToX[key], numpy.float ).T + else: + self.__H["AppliedToX"] = None + # + return 0 + + # ----------------------------------------------------------- + def setEvolutionModel(self, + asFunction = {"Direct":None, "Tangent":None, "Adjoint":None}, + asMatrix = None, + Scheduler = None, + ): + """ + Permet de définir un opérateur d'évolution M. L'ordre de priorité des + définitions et leur sens sont les suivants : + - si asFunction["Tangent"] et asFunction["Adjoint"] ne sont pas None + alors on définit l'opérateur à l'aide de fonctions. Si la fonction + "Direct" n'est pas définie, on prend la fonction "Tangent". + - si les fonctions ne sont pas disponibles et si asMatrix n'est pas + None, alors on définit l'opérateur "Direct" et "Tangent" à l'aide de + la matrice, et l'opérateur "Adjoint" à l'aide de la transposée. La + matrice fournie doit être sous une forme compatible avec le + constructeur de numpy.matrix. + """ + if (type(asFunction) is type({})) and (asFunction["Tangent"] is not None) and (asFunction["Adjoint"] is not None): + if not asFunction.has_key("Direct") or (asFunction["Direct"] is None): + self.__M["Direct"] = Operator( fromMethod = asFunction["Tangent"] ) + else: + self.__M["Direct"] = Operator( fromMethod = asFunction["Direct"] ) + self.__M["Tangent"] = Operator( fromMethod = asFunction["Tangent"] ) + self.__M["Adjoint"] = Operator( fromMethod = asFunction["Adjoint"] ) + elif asMatrix is not None: + matrice = numpy.matrix( asMatrix, numpy.float ) + self.__M["Direct"] = Operator( fromMatrix = matrice ) + self.__M["Tangent"] = Operator( fromMatrix = matrice ) + self.__M["Adjoint"] = Operator( fromMatrix = matrice.T ) + else: + raise ValueError("Error: improperly defined evolution operator") + return 0 + + def setEvolutionError(self, asCovariance=None): + """ + Permet de définir la covariance des erreurs de modèle : + - asCovariance : entrée des données, comme une matrice compatible avec + le constructeur de numpy.matrix + """ + self.__Q = numpy.matrix( asCovariance, numpy.float ) + return 0 + + # ----------------------------------------------------------- + def setControls (self, asVector = None ): + """ + Permet de définir la valeur initiale du vecteur X contenant toutes les + variables de contrôle, i.e. les paramètres ou l'état dont on veut + estimer la valeur pour obtenir les observations. C'est utile pour un + algorithme itératif/incrémental + - asVector : entrée des données, comme un vecteur compatible avec le + constructeur de numpy.matrix. + """ + if asVector is not None: + self.__X.store( asVector ) + return 0 + + # ----------------------------------------------------------- + def setAlgorithm(self, choice = None ): + """ + Permet de sélectionner l'algorithme à utiliser pour mener à bien l'étude + d'assimilation. L'argument est un champ caractère se rapportant au nom + d'un fichier contenu dans "../daAlgorithms" et réalisant l'opération + d'assimilation sur les arguments (Xb,Y,H,R,B,Xa). + """ + if choice is None: + raise ValueError("Error: algorithm choice has to be given") + if self.__algorithmName is not None: + raise ValueError("Error: algorithm choice has already been done as \"%s\", it can't be changed."%self.__algorithmName) + daDirectory = "daAlgorithms" + # + # Recherche explicitement le fichier complet + # ------------------------------------------ + module_path = None + for directory in sys.path: + if os.path.isfile(os.path.join(directory, daDirectory, str(choice)+'.py')): + module_path = os.path.abspath(os.path.join(directory, daDirectory)) + if module_path is None: + raise ImportError("No algorithm module named \"%s\" was found in a \"%s\" subdirectory\n The search path is %s"%(choice, daDirectory, sys.path)) + # + # Importe le fichier complet comme un module + # ------------------------------------------ + try: + sys_path_tmp = sys.path ; sys.path.insert(0,module_path) + self.__algorithmFile = __import__(str(choice), globals(), locals(), []) + self.__algorithmName = str(choice) + sys.path = sys_path_tmp ; del sys_path_tmp + except ImportError, e: + raise ImportError("The module named \"%s\" was found, but is incorrect at the import stage.\n The import error message is: %s"%(choice,e)) + # + # Instancie un objet du type élémentaire du fichier + # ------------------------------------------------- + self.__algorithm = self.__algorithmFile.ElementaryAlgorithm() + return 0 + + def setAlgorithmParameters(self, asDico=None): + """ + Permet de définir les paramètres de l'algorithme, sous la forme d'un + dictionnaire. + """ + self.__Parameters = dict( asDico ) + return 0 + + # ----------------------------------------------------------- + def setDiagnostic(self, choice = None, name = "", unit = "", basetype = None, parameters = {} ): + """ + Permet de sélectionner un diagnostic a effectuer. + """ + if choice is None: + raise ValueError("Error: diagnostic choice has to be given") + daDirectory = "daDiagnostics" + # + # Recherche explicitement le fichier complet + # ------------------------------------------ + module_path = None + for directory in sys.path: + if os.path.isfile(os.path.join(directory, daDirectory, str(choice)+'.py')): + module_path = os.path.abspath(os.path.join(directory, daDirectory)) + if module_path is None: + raise ImportError("No diagnostic module named \"%s\" was found in a \"%s\" subdirectory\n The search path is %s"%(choice, daDirectory, sys.path)) + # + # Importe le fichier complet comme un module + # ------------------------------------------ + try: + sys_path_tmp = sys.path ; sys.path.insert(0,module_path) + self.__diagnosticFile = __import__(str(choice), globals(), locals(), []) + sys.path = sys_path_tmp ; del sys_path_tmp + except ImportError, e: + raise ImportError("The module named \"%s\" was found, but is incorrect at the import stage.\n The import error message is: %s"%(choice,e)) + # + # Instancie un objet du type élémentaire du fichier + # ------------------------------------------------- + if self.__StoredDiagnostics.has_key(name): + raise ValueError("A diagnostic with the same name already exists") + else: + self.__StoredDiagnostics[name] = self.__diagnosticFile.ElementaryDiagnostic( + name = name, + unit = unit, + basetype = basetype, + parameters = parameters ) + return 0 + + # ----------------------------------------------------------- + def shape_validate(self): + """ + Validation de la correspondance correcte des tailles des variables et + des matrices s'il y en a. + """ + if self.__Xb is None: __Xb_shape = (0,) + elif hasattr(self.__Xb,"shape"): + if type(self.__Xb.shape) is tuple: __Xb_shape = self.__Xb.shape + else: __Xb_shape = self.__Xb.shape() + else: raise TypeError("Xb has no attribute of shape: problem !") + # + if self.__Y is None: __Y_shape = (0,) + elif hasattr(self.__Y,"shape"): + if type(self.__Y.shape) is tuple: __Y_shape = self.__Y.shape + else: __Y_shape = self.__Y.shape() + else: raise TypeError("Y has no attribute of shape: problem !") + # + if self.__B is None: __B_shape = (0,0) + elif hasattr(self.__B,"shape"): + if type(self.__B.shape) is tuple: __B_shape = self.__B.shape + else: __B_shape = self.__B.shape() + else: raise TypeError("B has no attribute of shape: problem !") + # + if self.__R is None: __R_shape = (0,0) + elif hasattr(self.__R,"shape"): + if type(self.__R.shape) is tuple: __R_shape = self.__R.shape + else: __R_shape = self.__R.shape() + else: raise TypeError("R has no attribute of shape: problem !") + # + if self.__Q is None: __Q_shape = (0,0) + elif hasattr(self.__Q,"shape"): + if type(self.__Q.shape) is tuple: __Q_shape = self.__Q.shape + else: __Q_shape = self.__Q.shape() + else: raise TypeError("Q has no attribute of shape: problem !") + # + if len(self.__H) == 0: __H_shape = (0,0) + elif type(self.__H) is type({}): __H_shape = (0,0) + elif hasattr(self.__H["Direct"],"shape"): + if type(self.__H["Direct"].shape) is tuple: __H_shape = self.__H["Direct"].shape + else: __H_shape = self.__H["Direct"].shape() + else: raise TypeError("H has no attribute of shape: problem !") + # + if len(self.__M) == 0: __M_shape = (0,0) + elif type(self.__M) is type({}): __M_shape = (0,0) + elif hasattr(self.__M["Direct"],"shape"): + if type(self.__M["Direct"].shape) is tuple: __M_shape = self.__M["Direct"].shape + else: __M_shape = self.__M["Direct"].shape() + else: raise TypeError("M has no attribute of shape: problem !") + # + # Vérification des conditions + # --------------------------- + if not( len(__Xb_shape) == 1 or min(__Xb_shape) == 1 ): + raise ValueError("Shape characteristic of Xb is incorrect: \"%s\""%(__Xb_shape,)) + if not( len(__Y_shape) == 1 or min(__Y_shape) == 1 ): + raise ValueError("Shape characteristic of Y is incorrect: \"%s\""%(__Y_shape,)) + # + if not( min(__B_shape) == max(__B_shape) ): + raise ValueError("Shape characteristic of B is incorrect: \"%s\""%(__B_shape,)) + if not( min(__R_shape) == max(__R_shape) ): + raise ValueError("Shape characteristic of R is incorrect: \"%s\""%(__R_shape,)) + if not( min(__Q_shape) == max(__Q_shape) ): + raise ValueError("Shape characteristic of Q is incorrect: \"%s\""%(__Q_shape,)) + if not( min(__M_shape) == max(__M_shape) ): + raise ValueError("Shape characteristic of M is incorrect: \"%s\""%(__M_shape,)) + # + if len(self.__H) > 0 and not(type(self.__H) is type({})) and not( __H_shape[1] == max(__Xb_shape) ): + raise ValueError("Shape characteristic of H \"%s\" and X \"%s\" are incompatible"%(__H_shape,__Xb_shape)) + if len(self.__H) > 0 and not(type(self.__H) is type({})) and not( __H_shape[0] == max(__Y_shape) ): + raise ValueError("Shape characteristic of H \"%s\" and Y \"%s\" are incompatible"%(__H_shape,__Y_shape)) + if len(self.__H) > 0 and not(type(self.__H) is type({})) and len(self.__B) > 0 and not( __H_shape[1] == __B_shape[0] ): + raise ValueError("Shape characteristic of H \"%s\" and B \"%s\" are incompatible"%(__H_shape,__B_shape)) + if len(self.__H) > 0 and not(type(self.__H) is type({})) and len(self.__R) > 0 and not( __H_shape[0] == __R_shape[1] ): + raise ValueError("Shape characteristic of H \"%s\" and R \"%s\" are incompatible"%(__H_shape,__R_shape)) + # + if len(self.__B) > 0 and not( __B_shape[1] == max(__Xb_shape) ): + raise ValueError("Shape characteristic of B \"%s\" and Xb \"%s\" are incompatible"%(__B_shape,__Xb_shape)) + # + if len(self.__R) > 0 and not( __R_shape[1] == max(__Y_shape) ): + raise ValueError("Shape characteristic of R \"%s\" and Y \"%s\" are incompatible"%(__R_shape,__Y_shape)) + # + if len(self.__M) > 0 and not(type(self.__M) is type({})) and not( __M_shape[1] == max(__Xb_shape) ): + raise ValueError("Shape characteristic of M \"%s\" and X \"%s\" are incompatible"%(__M_shape,__Xb_shape)) + # + return 1 + + # ----------------------------------------------------------- + def analyze(self): + """ + Permet de lancer le calcul d'assimilation. + + Le nom de la méthode à activer est toujours "run". Les paramètres en + arguments de la méthode sont fixés. En sortie, on obtient les résultats + dans la variable de type dictionnaire "StoredVariables", qui contient en + particulier des objets de Persistence pour les analyses, OMA... + """ + self.shape_validate() + # + self.__algorithm.run( + Xb = self.__Xb, + Y = self.__Y, + H = self.__H, + M = self.__M, + R = self.__R, + B = self.__B, + Q = self.__Q, + Par = self.__Parameters, + ) + return 0 + + # ----------------------------------------------------------- + def get(self, key=None): + """ + Renvoie les résultats disponibles après l'exécution de la méthode + d'assimilation, ou les diagnostics disponibles. Attention, quand un + diagnostic porte le même nom qu'un variable stockée, c'est la variable + stockée qui est renvoyée, et le diagnostic est inatteignable. + """ + if key is not None: + if self.__algorithm.has_key(key): + return self.__algorithm.get( key ) + elif self.__StoredDiagnostics.has_key(key): + return self.__StoredDiagnostics[key] + else: + raise ValueError("The requested key \"%s\" does not exists as a diagnostic or as a stored variable."%key) + else: + allvariables = self.__algorithm.get() + allvariables.update( self.__StoredDiagnostics ) + return allvariables + + def get_available_algorithms(self): + """ + Renvoie la liste des algorithmes identifiés par les chaînes de + caractères + """ + files = [] + for directory in sys.path: + if os.path.isdir(os.path.join(directory,"daAlgorithms")): + for fname in os.listdir(os.path.join(directory,"daAlgorithms")): + root, ext = os.path.splitext(fname) + if ext == '.py' and root != '__init__': + files.append(root) + files.sort() + return files + + def get_available_diagnostics(self): + """ + Renvoie la liste des diagnostics identifiés par les chaînes de + caractères + """ + files = [] + for directory in sys.path: + if os.path.isdir(os.path.join(directory,"daDiagnostics")): + for fname in os.listdir(os.path.join(directory,"daDiagnostics")): + root, ext = os.path.splitext(fname) + if ext == '.py' and root != '__init__': + files.append(root) + files.sort() + return files + + # ----------------------------------------------------------- + def get_algorithms_main_path(self): + """ + Renvoie le chemin pour le répertoire principal contenant les algorithmes + dans un sous-répertoire "daAlgorithms" + """ + return self.__parent + + def add_algorithms_path(self, asPath=None): + """ + Ajoute au chemin de recherche des algorithmes un répertoire dans lequel + se trouve un sous-répertoire "daAlgorithms" + + Remarque : si le chemin a déjà été ajouté pour les diagnostics, il n'est + pas indispensable de le rajouter ici. + """ + if not os.path.isdir(asPath): + raise ValueError("The given "+asPath+" argument must exist as a directory") + if not os.path.isdir(os.path.join(asPath,"daAlgorithms")): + raise ValueError("The given \""+asPath+"\" argument must contain a subdirectory named \"daAlgorithms\"") + if not os.path.isfile(os.path.join(asPath,"daAlgorithms","__init__.py")): + raise ValueError("The given \""+asPath+"/daAlgorithms\" path must contain a file named \"__init__.py\"") + sys.path.insert(0, os.path.abspath(asPath)) + sys.path = list(set(sys.path)) # Conserve en unique exemplaire chaque chemin + return 1 + + def get_diagnostics_main_path(self): + """ + Renvoie le chemin pour le répertoire principal contenant les diagnostics + dans un sous-répertoire "daDiagnostics" + """ + return self.__parent + + def add_diagnostics_path(self, asPath=None): + """ + Ajoute au chemin de recherche des algorithmes un répertoire dans lequel + se trouve un sous-répertoire "daDiagnostics" + + Remarque : si le chemin a déjà été ajouté pour les algorithmes, il n'est + pas indispensable de le rajouter ici. + """ + if not os.path.isdir(asPath): + raise ValueError("The given "+asPath+" argument must exist as a directory") + if not os.path.isdir(os.path.join(asPath,"daDiagnostics")): + raise ValueError("The given \""+asPath+"\" argument must contain a subdirectory named \"daDiagnostics\"") + if not os.path.isfile(os.path.join(asPath,"daDiagnostics","__init__.py")): + raise ValueError("The given \""+asPath+"/daDiagnostics\" path must contain a file named \"__init__.py\"") + sys.path.insert(0, os.path.abspath(asPath)) + sys.path = list(set(sys.path)) # Conserve en unique exemplaire chaque chemin + return 1 + +# ============================================================================== +if __name__ == "__main__": + print '\n AUTODIAGNOSTIC \n' + + ADD = AssimilationStudy("Ma premiere etude BLUE") + + ADD.setBackground (asVector = [0, 1, 2]) + ADD.setBackgroundError (asCovariance = "1 0 0;0 1 0;0 0 1") + ADD.setObservation (asVector = [0.5, 1.5, 2.5]) + ADD.setObservationError (asCovariance = "1 0 0;0 1 0;0 0 1") + ADD.setObservationOperator(asMatrix = "1 0 0;0 1 0;0 0 1") + + ADD.setAlgorithm(choice="Blue") + + ADD.analyze() + + print "Nombre d'analyses :", ADD.get("Analysis").stepnumber() + print "Analyse résultante :", ADD.get("Analysis").valueserie(0) + print "Innovation :", ADD.get("Innovation").valueserie(0) + print + + print "Algorithmes disponibles :", ADD.get_available_algorithms() + # print " Chemin des algorithmes :", ADD.get_algorithms_main_path() + print "Diagnostics disponibles :", ADD.get_available_diagnostics() + # print " Chemin des diagnostics :", ADD.get_diagnostics_main_path() + print + + ADD.setDiagnostic("RMS", "Ma RMS") + + liste = ADD.get().keys() + liste.sort() + print "Variables et diagnostics disponibles :", liste + print + diff --git a/src/daComposant/daCore/BasicObjects.py b/src/daComposant/daCore/BasicObjects.py new file mode 100644 index 0000000..bdcae37 --- /dev/null +++ b/src/daComposant/daCore/BasicObjects.py @@ -0,0 +1,213 @@ +#-*-coding:iso-8859-1-*- +# +# Copyright (C) 2008-2009 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +__doc__ = """ + Définit les outils généraux élémentaires. + + Ce module est destiné à etre appelée par AssimilationStudy pour constituer + les objets élémentaires de l'algorithme. +""" +__author__ = "Jean-Philippe ARGAUD - Mars 2008" + +import numpy +import Persistence + +# ============================================================================== +class Operator: + """ + Classe générale d'interface de type opérateur + """ + def __init__(self, fromMethod=None, fromMatrix=None): + """ + On construit un objet de ce type en fournissant à l'aide de l'un des + deux mots-clé, soit une fonction python, soit matrice. + Arguments : + - fromMethod : argument de type fonction Python + - fromMatrix : argument adapté au constructeur numpy.matrix + """ + if fromMethod is not None: + self.__Method = fromMethod + self.__Matrix = None + elif fromMatrix is not None: + self.__Method = None + self.__Matrix = numpy.matrix( fromMatrix, numpy.float ) + else: + self.__Method = None + self.__Matrix = None + + def appliedTo(self, xValue): + """ + Permet de restituer le résultat de l'application de l'opérateur à un + argument xValue. Cette méthode se contente d'appliquer, son argument + devant a priori être du bon type. + Arguments : + - xValue : argument adapté pour appliquer l'opérateur + """ + if self.__Matrix is not None: + return self.__Matrix * xValue + else: + return self.__Method( xValue ) + + def appliedInXTo(self, (xNominal, xValue) ): + """ + Permet de restituer le résultat de l'application de l'opérateur à un + argument xValue, sachant que l'opérateur est valable en xNominal. + Cette méthode se contente d'appliquer, son argument devant a priori + être du bon type. Si l'opérateur est linéaire car c'est une matrice, + alors il est valable en tout point nominal et il n'est pas nécessaire + d'utiliser xNominal. + Arguments : une liste contenant + - xNominal : argument permettant de donner le point où l'opérateur + est construit pour etre ensuite appliqué + - xValue : argument adapté pour appliquer l'opérateur + """ + if self.__Matrix is not None: + return self.__Matrix * xValue + else: + return self.__Method( (xNominal, xValue) ) + + def asMatrix(self): + """ + Permet de renvoyer l'opérateur sous la forme d'une matrice + """ + if self.__Matrix is not None: + return self.__Matrix + else: + raise ValueError("Matrix form of the operator is not available") + + def shape(self): + """ + Renvoie la taille sous forme numpy si l'opérateur est disponible sous + la forme d'une matrice + """ + if self.__Matrix is not None: + return self.__Matrix.shape + else: + raise ValueError("Matrix form of the operator is not available, nor the shape") + +# ============================================================================== +class Algorithm: + """ + Classe générale d'interface de type algorithme + + Elle donne un cadre pour l'écriture d'une classe élémentaire d'algorithme + d'assimilation, en fournissant un container (dictionnaire) de variables + persistantes initialisées, et des méthodes d'accès à ces variables stockées. + + Une classe élémentaire d'algorithme doit implémenter la méthode "run". + """ + def __init__(self): + """ + L'initialisation présente permet de fabriquer des variables de stockage + disponibles de manière générique dans les algorithmes élémentaires. Ces + variables de stockage sont ensuite conservées dans un dictionnaire + interne à l'objet, mais auquel on accède par la méthode "get". + + Les variables prévues sont : + - Analysis : l'analyse + - Innovation : l'innovation : d = Y - H Xb + - SigmaObs2 : correction optimale des erreurs d'observation + - SigmaBck2 : correction optimale des erreurs d'ébauche + - OMA : Observation moins Analysis : Y - Xa + - OMB : Observation moins Background : Y - Xb + - AMB : Analysis moins Background : Xa - Xb + - CovarianceAPosteriori : matrice A + On peut rajouter des variables à stocker dans l'initialisation de + l'algorithme élémentaire qui va hériter de cette classe + """ + self.StoredVariables = {} + self.StoredVariables["CostFunctionJ"] = Persistence.OneScalar(name = "CostFunctionJ") + self.StoredVariables["CostFunctionJb"] = Persistence.OneScalar(name = "CostFunctionJb") + self.StoredVariables["CostFunctionJo"] = Persistence.OneScalar(name = "CostFunctionJo") + self.StoredVariables["GradientOfCostFunctionJ"] = Persistence.OneScalar(name = "GradientOfCostFunctionJ") + self.StoredVariables["GradientOfCostFunctionJb"] = Persistence.OneScalar(name = "GradientOfCostFunctionJb") + self.StoredVariables["GradientOfCostFunctionJo"] = Persistence.OneScalar(name = "GradientOfCostFunctionJo") + self.StoredVariables["Analysis"] = Persistence.OneVector(name = "Analysis") + self.StoredVariables["Innovation"] = Persistence.OneVector(name = "Innovation") + self.StoredVariables["SigmaObs2"] = Persistence.OneScalar(name = "SigmaObs2") + self.StoredVariables["SigmaBck2"] = Persistence.OneScalar(name = "SigmaBck2") + self.StoredVariables["OMA"] = Persistence.OneVector(name = "OMA") + self.StoredVariables["OMB"] = Persistence.OneVector(name = "OMB") + self.StoredVariables["BMA"] = Persistence.OneVector(name = "BMA") + self.StoredVariables["CovarianceAPosteriori"] = Persistence.OneMatrix(name = "CovarianceAPosteriori") + self._name = None + + def get(self, key=None): + """ + Renvoie l'une des variables stockées identifiée par la clé, ou le + dictionnaire de l'ensemble des variables disponibles en l'absence de + clé. Ce sont directement les variables sous forme objet qui sont + renvoyées, donc les méthodes d'accès à l'objet individuel sont celles + des classes de persistance. + """ + if key is not None: + return self.StoredVariables[key] + else: + return self.StoredVariables + + def has_key(self, key=None): + """ + Vérifie si l'une des variables stockées est identifiée par la clé. + """ + return self.StoredVariables.has_key(key) + + def run(self, Xb=None, Y=None, H=None, M=None, R=None, B=None, Q=None, Par=None): + """ + Doit implémenter l'opération élémentaire de calcul d'assimilation sous + sa forme mathématique la plus naturelle possible. + """ + raise NotImplementedError("Mathematical assimilation calculation has not been implemented!") + +# ============================================================================== +class Diagnostic: + """ + Classe générale d'interface de type diagnostic + + Ce template s'utilise de la manière suivante : il sert de classe "patron" en + même temps que l'une des classes de persistance, comme "OneScalar" par + exemple. + + Une classe élémentaire de diagnostic doit implémenter ses deux méthodes, la + méthode "_formula" pour écrire explicitement et proprement la formule pour + l'écriture mathématique du calcul du diagnostic (méthode interne non + publique), et "calculate" pour activer la précédente tout en ayant vérifié + et préparé les données, et pour stocker les résultats à chaque pas (méthode + externe d'activation). + """ + def __init__(self, name = "", parameters = {}): + self.name = str(name) + self.parameters = dict( parameters ) + + def _formula(self, *args): + """ + Doit implémenter l'opération élémentaire de diagnostic sous sa forme + mathématique la plus naturelle possible. + """ + raise NotImplementedError("Diagnostic mathematical formula has not been implemented!") + + def calculate(self, *args): + """ + Active la formule de calcul avec les arguments correctement rangés + """ + raise NotImplementedError("Diagnostic activation method has not been implemented!") + +# ============================================================================== +if __name__ == "__main__": + print '\n AUTODIAGNOSTIC \n' diff --git a/src/daComposant/daCore/Logging.py b/src/daComposant/daCore/Logging.py new file mode 100644 index 0000000..b56f932 --- /dev/null +++ b/src/daComposant/daCore/Logging.py @@ -0,0 +1,162 @@ +#-*-coding:iso-8859-1-*- +# +# Copyright (C) 2008-2009 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +__doc__ = """ + Ce module permet de mettre en place un logging utilisable partout dans + l'application, par défaut à la console, et si nécessaire dans un fichier. + + Il doit être appelé en premier dans AssimilationStudy (mais pas directement + dans les applications utilisateurs), en l'important et en instanciant un + objet : + import Logging ; Logging.Logging() + + Par défaut, seuls les messages du niveau WARNING ou au-delà sont disponibles + (donc les simples messages d'info ne sont pas disponibles), ce que l'on peut + changer à l'instanciation avec le mot-clé "level" : + import Logging ; Logging.Logging(level=20) + + On peut éventuellement demander à l'objet de sortir aussi les messages dans + un fichier (noms par défaut : AssimilationStudy.log, niveau NOTSET) : + import Logging ; Logging.Logging().setLogfile() + + Si on veut changer le nom du fichier ou le niveau global de message, il faut + récupérer l'instance et appliquer les méthodes : + import Logging + log = Logging.Logging() + import logging + log.setLevel(logging.DEBUG) + log.setLogfile(filename="toto.log", filemode="a", level=logging.WARNING) + et on change éventuellement le niveau avec : + log.setLogfileLevel(logging.INFO) + + Ensuite, n'importe où dans les applications, il suffit d'utiliser le module + "logging" (avec un petit "l") : + import logging + log = logging.getLogger(NAME) # Avec rien (recommandé) ou un nom NAME + log.critical("...") + log.error("...") + log.warning("...") + log.info("...") + log.debug("...") + ou encore plus simplement : + import logging + logging.info("...") + + Dans une application, à n'importe quel endroit et autant de fois qu'on veut, + on peut changer le niveau global de message en utilisant par exemple : + import logging + logging.setLevel(logging.DEBUG) + + On rappelle les niveaux (attributs de "logging") et leur ordre : + NOTSET=0 < DEBUG=10 < INFO=20 < WARNING=30 < ERROR=40 < CRITICAL=50 +""" +__author__ = "Jean-Philippe ARGAUD - Octobre 2008" + +import os +import sys +import logging +from PlatformInfo import PlatformInfo + +LOGFILE = os.path.join(os.path.abspath(os.curdir),"AssimilationStudy.log") + +# ============================================================================== +class Logging: + def __init__(self, level=logging.WARNING): + """ + Initialise un logging à la console pour TOUS les niveaux de messages. + """ + logging.basicConfig( + format = '%(levelname)-8s %(message)s', + level = level, + stream = sys.stdout, + ) + self.__logfile = None + # + # Initialise l'affichage de logging + # --------------------------------- + p = PlatformInfo() + # + logging.info( "--------------------------------------------------" ) + logging.info( "Lancement de "+p.getName()+" "+p.getVersion() ) + logging.info( "--------------------------------------------------" ) + logging.info( "Versions logicielles :" ) + logging.info( "- Python "+p.getPythonVersion() ) + logging.info( "- Numpy "+p.getNumpyVersion() ) + logging.info( "- Scipy "+p.getScipyVersion() ) + logging.info( "" ) + + def setLogfileLevel(self, level=logging.NOTSET ): + """ + Permet de changer globalement le niveau des messages disponibles. + """ + logging.getLogger().setLevel(level) + + def setLogfile(self, filename=LOGFILE, filemode="w", level=logging.NOTSET): + """ + Permet de disposer des messages dans un fichier EN PLUS de la console. + """ + if self.__logfile is not None: + # Supprime le précédent mode de stockage fichier s'il exsitait + logging.getLogger().removeHandler(self.__logfile) + self.__logfile = logging.FileHandler(filename, filemode) + self.__logfile.setLevel(level) + self.__logfile.setFormatter( + logging.Formatter('%(asctime)s %(levelname)-8s %(message)s', + '%d %b %Y %H:%M:%S')) + logging.getLogger().addHandler(self.__logfile) + + def setLogfileLevel(self, level=logging.NOTSET ): + """ + Permet de changer le niveau des messages stockés en fichier. Il ne sera + pris en compte que s'il est supérieur au niveau global. + """ + self.__logfile.setLevel(level) + + def getLevel(self): + """ + Renvoie le niveau de Logging sous forme texte + """ + return logging.getLevelName( logging.getLogger().getEffectiveLevel() ) + +# ============================================================================== +if __name__ == "__main__": + print '\n AUTODIAGNOSTIC \n' + import os.path + + l = Logging(level = logging.NOTSET) + + logging.info("Message numéro 1 uniquement disponible sur console") + + l.setLogfile(level = logging.WARNING) + if not os.path.isfile(LOGFILE): + raise ValueError("Le fichier de log \"%s\" n'a pas pu être créé."%LOGFILE) + + logging.info("Message numéro 2 uniquement disponible sur console") + logging.warning("Message numéro 3 conjointement disponible sur console et fichier") + + l.setLogfileLevel(logging.INFO) + + logging.info("Message numéro 4 conjointement disponible sur console et fichier") + + print + print " Le logging a été correctement initialisé. Le fichier suivant" + print " %s"%os.path.basename(LOGFILE) + print " a été correctement créé, et peut être effacé après vérification." + print diff --git a/src/daComposant/daCore/Persistence.py b/src/daComposant/daCore/Persistence.py new file mode 100644 index 0000000..4f15a46 --- /dev/null +++ b/src/daComposant/daCore/Persistence.py @@ -0,0 +1,663 @@ +#-*-coding:iso-8859-1-*- +# +# Copyright (C) 2008-2009 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +__doc__ = """ + Définit des outils de persistence et d'enregistrement de séries de valeurs + pour analyse ultérieure ou utilisation de calcul. +""" +__author__ = "Jean-Philippe ARGAUD - Mars 2008" + +import numpy + +from PlatformInfo import PathManagement ; PathManagement() + +# ============================================================================== +class Persistence: + """ + Classe générale de persistence définissant les accesseurs nécessaires + (Template) + """ + def __init__(self, name="", unit="", basetype=str): + """ + name : nom courant + unit : unité + basetype : type de base de l'objet stocké à chaque pas + + La gestion interne des données est exclusivement basée sur les variables + initialisées ici (qui ne sont pas accessibles depuis l'extérieur des + objets comme des attributs) : + __step : numérotation par défaut du pas courant + __basetype : le type de base de chaque valeur, sous la forme d'un type + permettant l'instanciation ou le casting Python + __steps : les pas de stockage. Par défaut, c'est __step + __values : les valeurs de stockage. Par défaut, c'est None + """ + self.__name = str(name) + self.__unit = str(unit) + # + self.__step = -1 + self.__basetype = basetype + # + self.__steps = [] + self.__values = [] + + def basetype(self, basetype=None): + """ + Renvoie ou met en place le type de base des objets stockés + """ + if basetype is None: + return self.__basetype + else: + self.__basetype = basetype + + def store(self, value=None, step=None): + """ + Stocke une valeur à un pas. Une instanciation est faite avec le type de + base pour stocker l'objet. Si le pas n'est pas fournit, on utilise + l'étape de stockage comme valeur de pas. + """ + if value is None: raise ValueError("Value argument required") + self.__step += 1 + if step is not None: + self.__steps.append(step) + else: + self.__steps.append(self.__step) + # + self.__values.append(self.__basetype(value)) + + def shape(self): + """ + Renvoie la taille sous forme numpy du dernier objet stocké. Si c'est un + objet numpy, renvoie le shape. Si c'est un entier, un flottant, un + complexe, renvoie 1. Si c'est une liste ou un dictionnaire, renvoie la + longueur. Par défaut, renvoie 1. + """ + if len(self.__values) > 0: + if self.__basetype in [numpy.matrix, numpy.array]: + return self.__values[-1].shape + elif self.__basetype in [int, float]: + return (1,) + elif self.__basetype in [list, dict]: + return (len(self.__values[-1]),) + else: + return (1,) + else: + raise ValueError("Object has no shape before its first storage") + + def __len__(self): + """ + Renvoie le nombre d'éléments dans un séquence ou la plus grande + dimension d'une matrice + """ + return max( self.shape() ) + + # --------------------------------------------------------- + def stepserie(self, item=None, step=None): + """ + Renvoie par défaut toute la liste des pas de temps. Si l'argument "step" + existe dans la liste des pas de stockage effectués, renvoie ce pas + "step". Si l'argument "item" est correct, renvoie le pas stockée au + numéro "item". + """ + if step is not None and step in self.__steps: + return step + elif item is not None and item < len(self.__steps): + return self.__steps[item] + else: + return self.__steps + + def valueserie(self, item=None, step=None): + """ + Renvoie par défaut toute la liste des valeurs/objets. Si l'argument + "step" existe dans la liste des pas de stockage effectués, renvoie la + valeur stockée à ce pas "step". Si l'argument "item" est correct, + renvoie la valeur stockée au numéro "item". + """ + if step is not None and step in self.__steps: + index = self.__steps.index(step) + return self.__values[index] + elif item is not None and item < len(self.__values): + return self.__values[item] + else: + return self.__values + + def stepnumber(self): + """ + Renvoie le nombre de pas de stockage. + """ + return len(self.__steps) + + # --------------------------------------------------------- + def mean(self): + """ + Renvoie la valeur moyenne des données à chaque pas. Il faut que le type + de base soit compatible avec les types élémentaires numpy. + """ + try: + return [numpy.matrix(item).mean() for item in self.__values] + except: + raise TypeError("Base type is incompatible with numpy") + + def std(self, ddof=0): + """ + Renvoie l'écart-type des données à chaque pas. Il faut que le type de + base soit compatible avec les types élémentaires numpy. + + ddof : c'est le nombre de degrés de liberté pour le calcul de + l'écart-type, qui est dans le diviseur. Inutile avant Numpy 1.1 + """ + try: + if numpy.version.version >= '1.1.0': + return [numpy.matrix(item).std(ddof=ddof) for item in self.__values] + else: + return [numpy.matrix(item).std() for item in self.__values] + except: + raise TypeError("Base type is incompatible with numpy") + + def sum(self): + """ + Renvoie la somme des données à chaque pas. Il faut que le type de + base soit compatible avec les types élémentaires numpy. + """ + try: + return [numpy.matrix(item).sum() for item in self.__values] + except: + raise TypeError("Base type is incompatible with numpy") + + def min(self): + """ + Renvoie le minimum des données à chaque pas. Il faut que le type de + base soit compatible avec les types élémentaires numpy. + """ + try: + return [numpy.matrix(item).min() for item in self.__values] + except: + raise TypeError("Base type is incompatible with numpy") + + def max(self): + """ + Renvoie le maximum des données à chaque pas. Il faut que le type de + base soit compatible avec les types élémentaires numpy. + """ + try: + return [numpy.matrix(item).max() for item in self.__values] + except: + raise TypeError("Base type is incompatible with numpy") + + def plot(self, item=None, step=None, + steps = None, + title = "", + xlabel = "", + ylabel = "", + ltitle = None, + geometry = "600x400", + filename = "", + persist = False, + pause = True, + ): + """ + Renvoie un affichage de la valeur à chaque pas, si elle est compatible + avec un affichage Gnuplot (donc essentiellement un vecteur). Si + l'argument "step" existe dans la liste des pas de stockage effectués, + renvoie l'affichage de la valeur stockée à ce pas "step". Si l'argument + "item" est correct, renvoie l'affichage de la valeur stockée au numéro + "item". Par défaut ou en l'absence de "step" ou "item", renvoie un + affichage successif de tous les pas. + + Arguments : + - step : valeur du pas à afficher + - item : index de la valeur à afficher + - steps : liste unique des pas de l'axe des X, ou None si c'est + la numérotation par défaut + - title : base du titre général, qui sera automatiquement + complétée par la mention du pas + - xlabel : label de l'axe des X + - ylabel : label de l'axe des Y + - ltitle : titre associé au vecteur tracé + - geometry : taille en pixels de la fenêtre et position du coin haut + gauche, au format X11 : LxH+X+Y (défaut : 600x400) + - filename : base de nom de fichier Postscript pour une sauvegarde, + qui est automatiquement complétée par le numéro du + fichier calculé par incrément simple de compteur + - persist : booléen indiquant que la fenêtre affichée sera + conservée lors du passage au dessin suivant + Par défaut, persist = False + - pause : booléen indiquant une pause après chaque tracé, et + attendant un Return + Par défaut, pause = True + """ + import os + # + # Vérification de la disponibilité du module Gnuplot + try: + import Gnuplot + self.__gnuplot = Gnuplot + except: + raise ImportError("The Gnuplot module is required to plot the object.") + # + # Vérification et compléments sur les paramètres d'entrée + if persist: + self.__gnuplot.GnuplotOpts.gnuplot_command = 'gnuplot -persist -geometry '+geometry + else: + self.__gnuplot.GnuplotOpts.gnuplot_command = 'gnuplot -geometry '+geometry + if ltitle is None: + ltitle = "" + self.__g = self.__gnuplot.Gnuplot() # persist=1 + self.__g('set terminal '+self.__gnuplot.GnuplotOpts.default_term) + self.__g('set style data lines') + self.__g('set grid') + self.__g('set autoscale') + self.__g('set xlabel "'+str(xlabel).encode('ascii','replace')+'"') + self.__g('set ylabel "'+str(ylabel).encode('ascii','replace')+'"') + # + # Tracé du ou des vecteurs demandés + indexes = [] + if step is not None and step in self.__steps: + indexes.append(self.__steps.index(step)) + elif item is not None and item < len(self.__values): + indexes.append(item) + else: + indexes = indexes + range(len(self.__values)) + # + i = -1 + for index in indexes: + self.__g('set title "'+str(title).encode('ascii','replace')+' (pas '+str(index)+')"') + if ( type(steps) is type([]) ) or ( type(steps) is type(numpy.array([])) ): + Steps = list(steps) + else: + Steps = range(len(self.__values[index])) + # + self.__g.plot( self.__gnuplot.Data( Steps, self.__values[index], title=ltitle ) ) + # + if filename != "": + i += 1 + stepfilename = "%s_%03i.ps"%(filename,i) + if os.path.isfile(stepfilename): + raise ValueError("Error: a file with this name \"%s\" already exists."%stepfilename) + self.__g.hardcopy(filename=stepfilename, color=1) + if pause: + raw_input('Please press return to continue...\n') + + # --------------------------------------------------------- + def stepmean(self): + """ + Renvoie la moyenne sur toutes les valeurs sans tenir compte de la + longueur des pas. Il faut que le type de base soit compatible avec + les types élémentaires numpy. + """ + try: + return numpy.matrix(self.__values).mean() + except: + raise TypeError("Base type is incompatible with numpy") + + def stepstd(self, ddof=0): + """ + Renvoie l'écart-type de toutes les valeurs sans tenir compte de la + longueur des pas. Il faut que le type de base soit compatible avec + les types élémentaires numpy. + + ddof : c'est le nombre de degrés de liberté pour le calcul de + l'écart-type, qui est dans le diviseur. Inutile avant Numpy 1.1 + """ + try: + if numpy.version.version >= '1.1.0': + return numpy.matrix(self.__values).std(ddof=ddof) + else: + return numpy.matrix(self.__values).std() + except: + raise TypeError("Base type is incompatible with numpy") + + def stepsum(self): + """ + Renvoie la somme de toutes les valeurs sans tenir compte de la + longueur des pas. Il faut que le type de base soit compatible avec + les types élémentaires numpy. + """ + try: + return numpy.matrix(self.__values).sum() + except: + raise TypeError("Base type is incompatible with numpy") + + def stepmin(self): + """ + Renvoie le minimum de toutes les valeurs sans tenir compte de la + longueur des pas. Il faut que le type de base soit compatible avec + les types élémentaires numpy. + """ + try: + return numpy.matrix(self.__values).min() + except: + raise TypeError("Base type is incompatible with numpy") + + def stepmax(self): + """ + Renvoie le maximum de toutes les valeurs sans tenir compte de la + longueur des pas. Il faut que le type de base soit compatible avec + les types élémentaires numpy. + """ + try: + return numpy.matrix(self.__values).max() + except: + raise TypeError("Base type is incompatible with numpy") + + def cumsum(self): + """ + Renvoie la somme cumulée de toutes les valeurs sans tenir compte de la + longueur des pas. Il faut que le type de base soit compatible avec + les types élémentaires numpy. + """ + try: + return numpy.matrix(self.__values).cumsum(axis=0) + except: + raise TypeError("Base type is incompatible with numpy") + + # On pourrait aussi utiliser les autres attributs d'une "matrix", comme + # "tofile", "min"... + + def stepplot(self, + steps = None, + title = "", + xlabel = "", + ylabel = "", + ltitle = None, + geometry = "600x400", + filename = "", + persist = False, + pause = True, + ): + """ + Renvoie un affichage unique pour l'ensemble des valeurs à chaque pas, si + elles sont compatibles avec un affichage Gnuplot (donc essentiellement + un vecteur). Si l'argument "step" existe dans la liste des pas de + stockage effectués, renvoie l'affichage de la valeur stockée à ce pas + "step". Si l'argument "item" est correct, renvoie l'affichage de la + valeur stockée au numéro "item". + + Arguments : + - steps : liste unique des pas de l'axe des X, ou None si c'est + la numérotation par défaut + - title : base du titre général, qui sera automatiquement + complétée par la mention du pas + - xlabel : label de l'axe des X + - ylabel : label de l'axe des Y + - ltitle : titre associé au vecteur tracé + - geometry : taille en pixels de la fenêtre et position du coin haut + gauche, au format X11 : LxH+X+Y (défaut : 600x400) + - filename : nom de fichier Postscript pour une sauvegarde, + - persist : booléen indiquant que la fenêtre affichée sera + conservée lors du passage au dessin suivant + Par défaut, persist = False + - pause : booléen indiquant une pause après chaque tracé, et + attendant un Return + Par défaut, pause = True + """ + import os + # + # Vérification de la disponibilité du module Gnuplot + try: + import Gnuplot + self.__gnuplot = Gnuplot + except: + raise ImportError("The Gnuplot module is required to plot the object.") + # + # Vérification et compléments sur les paramètres d'entrée + if persist: + self.__gnuplot.GnuplotOpts.gnuplot_command = 'gnuplot -persist -geometry '+geometry + else: + self.__gnuplot.GnuplotOpts.gnuplot_command = 'gnuplot -geometry '+geometry + if ltitle is None: + ltitle = "" + if ( type(steps) is type([]) ) or ( type(steps) is type(numpy.array([])) ): + Steps = list(steps) + else: + Steps = range(len(self.__values[0])) + self.__g = self.__gnuplot.Gnuplot() # persist=1 + self.__g('set terminal '+self.__gnuplot.GnuplotOpts.default_term) + self.__g('set style data lines') + self.__g('set grid') + self.__g('set autoscale') + self.__g('set title "'+str(title).encode('ascii','replace') +'"') + self.__g('set xlabel "'+str(xlabel).encode('ascii','replace')+'"') + self.__g('set ylabel "'+str(ylabel).encode('ascii','replace')+'"') + # + # Tracé du ou des vecteurs demandés + indexes = range(len(self.__values)) + self.__g.plot( self.__gnuplot.Data( Steps, self.__values[indexes.pop(0)], title=ltitle+" (pas 0)" ) ) + for index in indexes: + self.__g.replot( self.__gnuplot.Data( Steps, self.__values[index], title=ltitle+" (pas %i)"%index ) ) + # + if filename != "": + self.__g.hardcopy(filename=filename, color=1) + if pause: + raw_input('Please press return to continue...\n') + +# ============================================================================== +class OneScalar(Persistence): + """ + Classe définissant le stockage d'une valeur unique réelle (float) par pas + + Le type de base peut être changé par la méthode "basetype", mais il faut que + le nouveau type de base soit compatible avec les types par éléments de + numpy. On peut même utiliser cette classe pour stocker des vecteurs/listes + ou des matrices comme dans les classes suivantes, mais c'est déconseillé + pour conserver une signification claire des noms. + """ + def __init__(self, name="", unit="", basetype = float): + Persistence.__init__(self, name, unit, basetype) + +class OneVector(Persistence): + """ + Classe définissant le stockage d'une liste (list) de valeurs homogènes par + hypothèse par pas. Pour éviter les confusions, ne pas utiliser la classe + "OneVector" pour des données hétérogènes, mais bien "OneList". + """ + def __init__(self, name="", unit="", basetype = list): + Persistence.__init__(self, name, unit, basetype) + +class OneMatrix(Persistence): + """ + Classe définissant le stockage d'une matrice de valeurs (numpy.matrix) par + pas + """ + def __init__(self, name="", unit="", basetype = numpy.matrix): + Persistence.__init__(self, name, unit, basetype) + +class OneList(Persistence): + """ + Classe définissant le stockage d'une liste de valeurs potentiellement + hétérogènes (list) par pas. Pour éviter les confusions, ne pas utiliser la + classe "OneVector" pour des données hétérogènes, mais bien "OneList". + """ + def __init__(self, name="", unit="", basetype = list): + Persistence.__init__(self, name, unit, basetype) + +# ============================================================================== +if __name__ == "__main__": + print '\n AUTODIAGNOSTIC \n' + + print "======> Un flottant" + OBJET_DE_TEST = OneScalar("My float", unit="cm") + OBJET_DE_TEST.store( 5.) + OBJET_DE_TEST.store(-5.) + OBJET_DE_TEST.store( 1.) + print "Les pas de stockage :", OBJET_DE_TEST.stepserie() + print "Les valeurs :", OBJET_DE_TEST.valueserie() + print "La 2ème valeur :", OBJET_DE_TEST.valueserie(1) + print "La dernière valeur :", OBJET_DE_TEST.valueserie(-1) + print "Valeurs par pas :" + print " La moyenne :", OBJET_DE_TEST.mean() + print " L'écart-type :", OBJET_DE_TEST.std() + print " La somme :", OBJET_DE_TEST.sum() + print " Le minimum :", OBJET_DE_TEST.min() + print " Le maximum :", OBJET_DE_TEST.max() + print "Valeurs globales :" + print " La moyenne :", OBJET_DE_TEST.stepmean() + print " L'écart-type :", OBJET_DE_TEST.stepstd() + print " La somme :", OBJET_DE_TEST.stepsum() + print " Le minimum :", OBJET_DE_TEST.stepmin() + print " Le maximum :", OBJET_DE_TEST.stepmax() + print " La somme cumulée :", OBJET_DE_TEST.cumsum() + print "Taille \"shape\" :", OBJET_DE_TEST.shape() + print "Taille \"len\" :", len(OBJET_DE_TEST) + del OBJET_DE_TEST + print + + print "======> Un entier" + OBJET_DE_TEST = OneScalar("My int", unit="cm", basetype=int) + OBJET_DE_TEST.store( 5 ) + OBJET_DE_TEST.store(-5 ) + OBJET_DE_TEST.store( 1.) + print "Les pas de stockage :", OBJET_DE_TEST.stepserie() + print "Les valeurs :", OBJET_DE_TEST.valueserie() + print "La 2ème valeur :", OBJET_DE_TEST.valueserie(1) + print "La dernière valeur :", OBJET_DE_TEST.valueserie(-1) + print "Valeurs par pas :" + print " La moyenne :", OBJET_DE_TEST.mean() + print " L'écart-type :", OBJET_DE_TEST.std() + print " La somme :", OBJET_DE_TEST.sum() + print " Le minimum :", OBJET_DE_TEST.min() + print " Le maximum :", OBJET_DE_TEST.max() + print "Valeurs globales :" + print " La moyenne :", OBJET_DE_TEST.stepmean() + print " L'écart-type :", OBJET_DE_TEST.stepstd() + print " La somme :", OBJET_DE_TEST.stepsum() + print " Le minimum :", OBJET_DE_TEST.stepmin() + print " Le maximum :", OBJET_DE_TEST.stepmax() + print " La somme cumulée :", OBJET_DE_TEST.cumsum() + print "Taille \"shape\" :", OBJET_DE_TEST.shape() + print "Taille \"len\" :", len(OBJET_DE_TEST) + del OBJET_DE_TEST + print + + print "======> Un booléen" + OBJET_DE_TEST = OneScalar("My bool", unit="", basetype=bool) + OBJET_DE_TEST.store( True ) + OBJET_DE_TEST.store( False ) + OBJET_DE_TEST.store( True ) + print "Les pas de stockage :", OBJET_DE_TEST.stepserie() + print "Les valeurs :", OBJET_DE_TEST.valueserie() + print "La 2ème valeur :", OBJET_DE_TEST.valueserie(1) + print "La dernière valeur :", OBJET_DE_TEST.valueserie(-1) + print "Taille \"shape\" :", OBJET_DE_TEST.shape() + print "Taille \"len\" :", len(OBJET_DE_TEST) + del OBJET_DE_TEST + print + + print "======> Un vecteur de flottants" + OBJET_DE_TEST = OneVector("My float vector", unit="cm") + OBJET_DE_TEST.store( (5 , -5) ) + OBJET_DE_TEST.store( (-5, 5 ) ) + OBJET_DE_TEST.store( (1., 1.) ) + print "Les pas de stockage :", OBJET_DE_TEST.stepserie() + print "Les valeurs :", OBJET_DE_TEST.valueserie() + print "La 2ème valeur :", OBJET_DE_TEST.valueserie(1) + print "La dernière valeur :", OBJET_DE_TEST.valueserie(-1) + print "Valeurs par pas :" + print " La moyenne :", OBJET_DE_TEST.mean() + print " L'écart-type :", OBJET_DE_TEST.std() + print " La somme :", OBJET_DE_TEST.sum() + print " Le minimum :", OBJET_DE_TEST.min() + print " Le maximum :", OBJET_DE_TEST.max() + print "Valeurs globales :" + print " La moyenne :", OBJET_DE_TEST.stepmean() + print " L'écart-type :", OBJET_DE_TEST.stepstd() + print " La somme :", OBJET_DE_TEST.stepsum() + print " Le minimum :", OBJET_DE_TEST.stepmin() + print " Le maximum :", OBJET_DE_TEST.stepmax() + print " La somme cumulée :", OBJET_DE_TEST.cumsum() + print "Taille \"shape\" :", OBJET_DE_TEST.shape() + print "Taille \"len\" :", len(OBJET_DE_TEST) + del OBJET_DE_TEST + print + + print "======> Une liste hétérogène" + OBJET_DE_TEST = OneList("My list", unit="bool/cm") + OBJET_DE_TEST.store( (True , -5) ) + OBJET_DE_TEST.store( (False, 5 ) ) + OBJET_DE_TEST.store( (True , 1.) ) + print "Les pas de stockage :", OBJET_DE_TEST.stepserie() + print "Les valeurs :", OBJET_DE_TEST.valueserie() + print "La 2ème valeur :", OBJET_DE_TEST.valueserie(1) + print "La dernière valeur :", OBJET_DE_TEST.valueserie(-1) + print "Valeurs par pas : attention, on peut les calculer car True=1, False=0, mais cela n'a pas de sens" + print " La moyenne :", OBJET_DE_TEST.mean() + print " L'écart-type :", OBJET_DE_TEST.std() + print " La somme :", OBJET_DE_TEST.sum() + print " Le minimum :", OBJET_DE_TEST.min() + print " Le maximum :", OBJET_DE_TEST.max() + print "Valeurs globales : attention, on peut les calculer car True=1, False=0, mais cela n'a pas de sens" + print " La moyenne :", OBJET_DE_TEST.stepmean() + print " L'écart-type :", OBJET_DE_TEST.stepstd() + print " La somme :", OBJET_DE_TEST.stepsum() + print " Le minimum :", OBJET_DE_TEST.stepmin() + print " Le maximum :", OBJET_DE_TEST.stepmax() + print " La somme cumulée :", OBJET_DE_TEST.cumsum() + print "Taille \"shape\" :", OBJET_DE_TEST.shape() + print "Taille \"len\" :", len(OBJET_DE_TEST) + del OBJET_DE_TEST + print + + print "======> Utilisation directe de la classe Persistence" + OBJET_DE_TEST = Persistence("My object", unit="", basetype=int ) + OBJET_DE_TEST.store( 1 ) + OBJET_DE_TEST.store( 3 ) + OBJET_DE_TEST.store( 7 ) + print "Les pas de stockage :", OBJET_DE_TEST.stepserie() + print "Les valeurs :", OBJET_DE_TEST.valueserie() + print "La 2ème valeur :", OBJET_DE_TEST.valueserie(1) + print "La dernière valeur :", OBJET_DE_TEST.valueserie(-1) + print "Taille \"shape\" :", OBJET_DE_TEST.shape() + print "Taille \"len\" :", len(OBJET_DE_TEST) + del OBJET_DE_TEST + print + + print "======> Affichage d'objets stockés" + OBJET_DE_TEST = Persistence("My object", unit="", basetype=numpy.array) + D = OBJET_DE_TEST + vect1 = [1, 2, 1, 2, 1] + vect2 = [-3, -3, 0, -3, -3] + vect3 = [-1, 1, -5, 1, -1] + vect4 = 100*[0.29, 0.97, 0.73, 0.01, 0.20] + print "Stockage de 3 vecteurs de longueur identique" + D.store(vect1) + D.store(vect2) + D.store(vect3) + print "Affichage de l'ensemble du stockage sur une même image" + D.stepplot( + title = "Tous les vecteurs", + filename="vecteurs.ps", + xlabel = "Axe X", + ylabel = "Axe Y", + pause = False ) + print "Stockage d'un quatrième vecteur de longueur différente" + D.store(vect4) + print "Affichage séparé du dernier stockage" + D.plot( + item = 3, + title = "Vecteurs", + filename = "vecteur", + xlabel = "Axe X", + ylabel = "Axe Y", + pause = False ) + print "Les images ont été stockées en fichiers Postscript" + print "Taille \"shape\" du dernier objet stocké",OBJET_DE_TEST.shape() + print "Taille \"len\" du dernier objet stocké",len(OBJET_DE_TEST) + del OBJET_DE_TEST + print diff --git a/src/daComposant/daCore/PlatformInfo.py b/src/daComposant/daCore/PlatformInfo.py new file mode 100644 index 0000000..6e50e12 --- /dev/null +++ b/src/daComposant/daCore/PlatformInfo.py @@ -0,0 +1,255 @@ +#-*-coding:iso-8859-1-*- +# +# Copyright (C) 2008-2009 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +__doc__ = """ + Informations sur le code et la plateforme, et mise à jour des chemins + + La classe "PlatformInfo" permet de récupérer les informations générales sur + le code et la plateforme sous forme de strings, ou d'afficher directement + les informations disponibles par les méthodes. L'impression directe d'un + objet de cette classe affiche les informations minimales. Par exemple : + print PlatformInfo() + print PlatformInfo().getVersion() + created = PlatformInfo().getDate() + + La classe "PathManagement" permet de mettre à jour les chemins système pour + ajouter les outils numériques, matrices... On l'utilise en instanciant + simplement cette classe, sans meme récupérer d'objet : + PathManagement() +""" +__author__ = "Jean-Philippe ARGAUD - Mars 2008" + +import os + +# ============================================================================== +class PlatformInfo: + """ + Rassemblement des informations sur le code et la plateforme + """ + def getName(self): + "Retourne le nom de l'application" + import version + return version.name + + def getVersion(self): + "Retourne le numéro de la version" + import version + return version.version + + def getDate(self): + "Retourne la date de création de la version" + import version + return version.date + + def getPythonVersion(self): + "Retourne la version de python utilisée" + import sys + return ".".join(map(str,sys.version_info[0:3])) + + def getNumpyVersion(self): + "Retourne la version de numpy utilisée" + import numpy.version + return numpy.version.version + + def getScipyVersion(self): + "Retourne la version de scipy utilisée" + import scipy.version + return scipy.version.version + + def getCurrentMemorySize(self): + "Retourne la taille mémoire courante utilisée" + return 1 + + def __str__(self): + import version + return "%s %s (%s)"%(version.name,version.version,version.date) + +# ============================================================================== +class PathManagement: + """ + Mise à jour du path système pour les répertoires d'outils + """ + def __init__(self): + import os, sys + parent = os.path.abspath(os.path.join(os.path.dirname(__file__),"..")) + self.__paths = {} + self.__paths["daExternals"] = os.path.join(parent,"daExternals") + self.__paths["daMatrices"] = os.path.join(parent,"daMatrices") + self.__paths["daNumerics"] = os.path.join(parent,"daNumerics") + # + for v in self.__paths.values(): + sys.path.insert(0, v ) + # + # Conserve en unique exemplaire chaque chemin + sys.path = list(set(sys.path)) + del parent + + def getpaths(self): + """ + Renvoie le dictionnaire des chemins ajoutés + """ + return self.__paths + +# ============================================================================== +class SystemUsage: + """ + Permet de récupérer les différentes tailles mémoires du process courant + """ + # + # Le module resource renvoie 0 pour les tailles mémoire. On utilise donc + # plutôt : http://code.activestate.com/recipes/286222/ et les infos de + # http://www.redhat.com/docs/manuals/enterprise/RHEL-4-Manual/en-US/Reference_Guide/s2-proc-meminfo.html + # + _proc_status = '/proc/%d/status' % os.getpid() + _memo_status = '/proc/meminfo' + _scale = { + 'o': 1.0, + 'ko': 1024.0, 'mo': 1024.0*1024.0, + 'Ko': 1024.0, 'Mo': 1024.0*1024.0, + 'B': 1.0, + 'kB': 1024.0, 'mB': 1024.0*1024.0, + 'KB': 1024.0, 'MB': 1024.0*1024.0, + } + _max_mem = 0 + _max_rss = 0 + _max_sta = 0 + # + def _VmA(self, VmKey, unit): + try: + t = open(self._memo_status) + v = t.read() + t.close() + except: + return 0.0 # non-Linux? + i = v.index(VmKey) # get VmKey line e.g. 'VmRSS: 9999 kB\n ...' + v = v[i:].split(None, 3) # whitespace + if len(v) < 3: + return 0.0 # invalid format? + # convert Vm value to bytes + mem = float(v[1]) * self._scale[v[2]] + return mem / self._scale[unit] + # + def getAvailablePhysicalMemory(self, unit="o"): + "Renvoie la mémoire physique utilisable en octets" + return self._VmA('MemTotal:', unit) + # + def getAvailableSwapMemory(self, unit="o"): + "Renvoie la mémoire swap utilisable en octets" + return self._VmA('SwapTotal:', unit) + # + def getAvailableMemory(self, unit="o"): + "Renvoie la mémoire totale (physique+swap) utilisable en octets" + return self._VmA('MemTotal:', unit) + self._VmA('SwapTotal:', unit) + # + def getUsableMemory(self, unit="o"): + """Renvoie la mémoire utilisable en octets + Rq : il n'est pas sûr que ce décompte soit juste... + """ + return self._VmA('MemFree:', unit) + self._VmA('SwapFree:', unit) + \ + self._VmA('Cached:', unit) + self._VmA('SwapCached:', unit) + # + def _VmB(self, VmKey, unit): + try: + t = open(self._proc_status) + v = t.read() + t.close() + except: + return 0.0 # non-Linux? + i = v.index(VmKey) # get VmKey line e.g. 'VmRSS: 9999 kB\n ...' + v = v[i:].split(None, 3) # whitespace + if len(v) < 3: + return 0.0 # invalid format? + # convert Vm value to bytes + mem = float(v[1]) * self._scale[v[2]] + return mem / self._scale[unit] + # + def getUsedMemory(self, unit="o"): + "Renvoie la mémoire totale utilisée en octets" + mem = self._VmB('VmSize:', unit) + self._max_mem = max(self._max_mem, mem) + return mem + # + def getUsedResident(self, unit="o"): + "Renvoie la mémoire résidente utilisée en octets" + mem = self._VmB('VmRSS:', unit) + self._max_rss = max(self._max_rss, mem) + return mem + # + def getUsedStacksize(self, unit="o"): + "Renvoie la taille du stack utilisé en octets" + mem = self._VmB('VmStk:', unit) + self._max_sta = max(self._max_sta, mem) + return mem + # + def getMaxUsedMemory(self): + "Renvoie la mémoire totale maximale mesurée" + return self._max_mem + # + def getMaxUsedResident(self): + "Renvoie la mémoire résidente maximale mesurée" + return self._max_rss + # + def getMaxUsedStacksize(self): + "Renvoie la mémoire du stack maximale mesurée" + return self._max_sta + +# ============================================================================== +if __name__ == "__main__": + print '\n AUTODIAGNOSTIC \n' + + print PlatformInfo() + print + p = PlatformInfo() + print "Les caractéristiques détaillées des applications et outils sont :" + print " - Application.......:",p.getName() + print " - Version...........:",p.getVersion() + print " - Date Application..:",p.getDate() + print " - Python............:",p.getPythonVersion() + print " - Numpy.............:",p.getNumpyVersion() + print " - Scipy.............:",p.getScipyVersion() + print + + p = PathManagement() + print "Les chemins ajoutés au système pour des outils :" + for k,v in p.getpaths().items(): + print " %12s : %s"%(k,os.path.basename(v)) + print + + m = SystemUsage() + print "La mémoire disponible est la suivante :" + print " - mémoire totale....: %4.1f Mo"%m.getAvailableMemory("Mo") + print " - mémoire physique..: %4.1f Mo"%m.getAvailablePhysicalMemory("Mo") + print " - mémoire swap......: %4.1f Mo"%m.getAvailableSwapMemory("Mo") + print " - utilisable........: %4.1f Mo"%m.getUsableMemory("Mo") + print "L'usage mémoire de cette exécution est le suivant :" + print " - mémoire totale....: %4.1f Mo"%m.getUsedMemory("Mo") + print " - mémoire résidente.: %4.1f Mo"%m.getUsedResident("Mo") + print " - taille de stack...: %4.1f Mo"%m.getUsedStacksize("Mo") + print "Création d'un objet range(1000000) et mesure mémoire" + x = range(1000000) + print " - mémoire totale....: %4.1f Mo"%m.getUsedMemory("Mo") + print "Destruction de l'objet et mesure mémoire" + del x + print " - mémoire totale....: %4.1f Mo"%m.getUsedMemory("Mo") + print "L'usage mémoire maximal de cette exécution est le suivant :" + print " - mémoire totale....: %4.1f Mo"%m.getMaxUsedMemory() + print " - mémoire résidente.: %4.1f Mo"%m.getMaxUsedResident() + print " - taille de stack...: %4.1f Mo"%m.getMaxUsedStacksize() + print diff --git a/src/daComposant/daCore/version.py b/src/daComposant/daCore/version.py new file mode 100644 index 0000000..7128d1a --- /dev/null +++ b/src/daComposant/daCore/version.py @@ -0,0 +1,23 @@ +#-*-coding:iso-8859-1-*- +# +# Copyright (C) 2008-2009 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +name = "Data Assimilation Package" +version = "0.2.0" +date = "lundi 23 septembre 2009, 11:11:11 (UTC+0200)" diff --git a/src/daComposant/daDiagnostics/CompareMeanDependantVectors.py b/src/daComposant/daDiagnostics/CompareMeanDependantVectors.py new file mode 100644 index 0000000..d2b0dc1 --- /dev/null +++ b/src/daComposant/daDiagnostics/CompareMeanDependantVectors.py @@ -0,0 +1,118 @@ +#-*-coding:iso-8859-1-*- +# +# Copyright (C) 2008-2009 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +__doc__ = """ + Diagnostic qui effectue le test d egalite des moyennes de 2 vecteurs + dependants au sens du test de Student. + Ce diagnostic utilise le calcul de la p-value pour le test de Student + pour 2 vecteurs dependants + En input : la tolerance + En output : le resultat du diagnostic est une reponse booleenne au test : + True si les moyennes sont egales au sens du Test de Student + False dans le cas contraire. +""" +__author__ = "Sophie RICCI - Octobre 2008" + +import sys ; sys.path.insert(0, "../daCore") + +import numpy +import Persistence +from BasicObjects import Diagnostic +from ComputeStudent import DependantVectors +import logging + +# ============================================================================== +class ElementaryDiagnostic(Diagnostic,Persistence.OneScalar): + """ + Diagnostic qui effectueIndependantVectorsEqualVariance le test d egalite des moyennes de 2 vecteurs + dependants au sens du test de Student. + Ce diagnostic utilise le calcul de la p-value pour le test de Student + pour 2 vecteurs dependants + En input : la tolerance + En output : le resultat du diagnostic est une reponse booleenne au test : + True si les moyennes sont egales au sens du Test de Student + False dans le cas contraire. + """ + def __init__(self, name="", unit="", basetype = None, parameters = {} ): + Diagnostic.__init__(self, name, parameters) + Persistence.OneScalar.__init__( self, name, unit, basetype = bool) + if not self.parameters.has_key("tolerance"): + raise ValueError("A parameter named \"tolerance\" is required.") + + def formula(self, V1, V2): + """ + Effectue le calcul de la p-value de Student pour deux vecteurs. + """ + [aire, Q, reponse, message] = DependantVectors( + vector1 = V1, + vector2 = V2, + tolerance = self.parameters["tolerance"] ) + logging.info( message ) + answerStudentTest = False + if (aire < (100.*self.parameters["tolerance"])) : + answerStudentTest = False + else: + answerStudentTest = True + return answerStudentTest + + def calculate(self, vector1 = None, vector2 = None, step = None): + """ + Active la formule de calcul + """ + if (vector1 is None) or (vector2 is None) : + raise ValueError("Two vectors must be given to calculate the Student value") + V1 = numpy.array(vector1) + V2 = numpy.array(vector2) + if (V1.size < 1) or (V2.size < 1): + raise ValueError("The given vectors must not be empty") + if V1.size != V2.size: + raise ValueError("The two given vectors must have the same size, or the vector types are incompatible") + value = self.formula( V1, V2 ) + self.store( value = value, step = step) + +# ============================================================================== +if __name__ == "__main__": + print '\n AUTODIAGNOSTIC \n' + + print " Test d'égalite des moyennes au sens de Student pour deux vecteurs" + print " dépendants." + print + # + # Initialisation des inputs et appel du diagnostic + # -------------------------------------------------------------------- + tolerance = 0.05 + D = ElementaryDiagnostic("ComputeMeanStudent_DependVect", parameters = { + "tolerance":tolerance, + }) + # + # Tirage de l'echantillon aleatoire + # -------------------------------------------------------------------- + x1 = numpy.array(([-0.23262176, 1.36065207, 0.32988102, 0.24400551, -0.66765848, -0.19088483, -0.31082575, 0.56849814, 1.21453443, 0.99657516])) + x2 = numpy.array(([-0.23, 1.36, 0.32, 0.24, -0.66, -0.19, -0.31, 0.56, 1.21, 0.99])) + # + # Calcul + # -------------------------------------------------------------------- + D.calculate(x1, x2) + # + if D.valueserie(0) : + print " L'hypothèse d'égalité des moyennes est valide." + print + else : + raise ValueError("The egality of the means is NOT valid") diff --git a/src/daComposant/daDiagnostics/CompareMeanIndependantVectorsDifferentVariance.py b/src/daComposant/daDiagnostics/CompareMeanIndependantVectorsDifferentVariance.py new file mode 100644 index 0000000..15e7865 --- /dev/null +++ b/src/daComposant/daDiagnostics/CompareMeanIndependantVectorsDifferentVariance.py @@ -0,0 +1,117 @@ +#-*-coding:iso-8859-1-*- +# +# Copyright (C) 2008-2009 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +__doc__ = """ + Diagnostic qui effectue le test d egalite des moyennes de 2 vecteurs + independants supposes de variances differentes au sens du test de Student. + En input : la tolerance + En output : le resultat du diagnostic est une reponse booleenne au test : + True si les moyennes sont egales au sens du Test de Student + False dans le cas contraire. +""" +__author__ = "Sophie RICCI - Octobre 2008" + +import sys ; sys.path.insert(0, "../daCore") + +import numpy +import Persistence +from BasicObjects import Diagnostic +from ComputeStudent import IndependantVectorsDifferentVariance +import logging + +# ============================================================================== +class ElementaryDiagnostic(Diagnostic,Persistence.OneScalar): + """ + Diagnostic qui effectue le test d egalite des moyennes de 2 vecteurs + independants supposes de variances differentes au sens du test de Student. + En input : la tolerance + En output : le resultat du diagnostic est une reponse booleenne au test : + True si les moyennes sont egales au sens du Test de Student + False dans le cas contraire. + """ + def __init__(self, name="", unit="", basetype = None, parameters = {} ): + Diagnostic.__init__(self, name, parameters) + Persistence.OneScalar.__init__( self, name, unit, basetype = bool) + if not self.parameters.has_key("tolerance"): + raise ValueError("A parameter named \"tolerance\" is required.") + + def formula(self, V1, V2): + """ + Effectue le calcul de la p-value de Student pour deux vecteurs + independants supposes de variances differentes. + """ + [aire, Q, reponse, message] = IndependantVectorsDifferentVariance( + vector1 = V1, + vector2 = V2, + tolerance = self.parameters["tolerance"], + ) + logging.info( message ) + answerStudentTest = False + if (aire < (100.*self.parameters["tolerance"])) : + answerStudentTest = False + else: + answerStudentTest = True + return answerStudentTest + + def calculate(self, vector1 = None, vector2 = None, step = None): + """ + Active la formule de calcul + """ + if (vector1 is None) or (vector2 is None) : + raise ValueError("Two vectors must be given to calculate the Student value") + V1 = numpy.array(vector1) + V2 = numpy.array(vector2) + if (V1.size < 1) or (V2.size < 1): + raise ValueError("The given vectors must not be empty") + if V1.size != V2.size: + raise ValueError("The two given vectors must have the same size, or the vector types are incompatible") + value = self.formula( V1, V2 ) + self.store( value = value, step = step) + +# ============================================================================== +if __name__ == "__main__": + print '\n AUTODIAGNOSTIC \n' + + print " Test d'égalite des moyennes au sens de Student pour deux vecteurs" + print " indépendants supposés de variances différentes." + print + # + # Initialisation des inputs et appel du diagnostic + # -------------------------------------------------------------------- + tolerance = 0.05 + D = ElementaryDiagnostic("IndependantVectorsDifferentVariance", parameters = { + "tolerance":tolerance, + }) + # + # Tirage de l'echantillon aleatoire + # -------------------------------------------------------------------- + x1 = numpy.array(([-0.23262176, 1.36065207, 0.32988102, 0.24400551, -0.66765848, -0.19088483, -0.31082575, 0.56849814, 1.21453443, 0.99657516])) + x2 = numpy.array(([-0.23, 1.36, 0.32, 0.24, -0.66, -0.19, -0.31, 0.56, 1.21, 0.99])) + # + # Calcul + # -------------------------------------------------------------------- + D.calculate(x1, x2) +# + if D.valueserie(0) : + print " L'hypothèse d'égalité des moyennes est valide." + print + else : + raise ValueError("The egality of the means is NOT valid") + diff --git a/src/daComposant/daDiagnostics/CompareMeanIndependantVectorsEqualVariance.py b/src/daComposant/daDiagnostics/CompareMeanIndependantVectorsEqualVariance.py new file mode 100644 index 0000000..927cba2 --- /dev/null +++ b/src/daComposant/daDiagnostics/CompareMeanIndependantVectorsEqualVariance.py @@ -0,0 +1,116 @@ +#-*-coding:iso-8859-1-*- +# +# Copyright (C) 2008-2009 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +__doc__ = """ + Diagnostic qui effectue le test d egalite des moyennes de 2 vecteurs + independants supposes de variances egales au sens du test de Student. + En input : la tolerance + En output : le resultat du diagnostic est une reponse booleenne au test : + True si les moyennes sont egales au sens du Test de Student + False dans le cas contraire. +""" +__author__ = "Sophie RICCI - Octobre 2008" + +import sys ; sys.path.insert(0, "../daCore") + +import numpy +import Persistence +from BasicObjects import Diagnostic +from ComputeStudent import IndependantVectorsEqualVariance +import logging + +# ============================================================================== +class ElementaryDiagnostic(Diagnostic,Persistence.OneScalar): + """ + Diagnostic qui effectue le test d egalite des moyennes de 2 vecteurs independants supposes de variances egales au sens du test de Student. + En input : la tolerance + En output : le resultat du diagnostic est une reponse booleenne au test : + True si les moyennes sont egales au sens du Test de Student + False dans le cas contraire. + """ + def __init__(self, name="", unit="", basetype = None, parameters = {} ): + Diagnostic.__init__(self, name, parameters) + Persistence.OneScalar.__init__( self, name, unit, basetype = bool) + if not self.parameters.has_key("tolerance"): + raise ValueError("A parameter named \"tolerance\" is required.") + + def formula(self, V1, V2): + """ + Effectue le calcul de la p-value de Student pour deux vecteurs + independants supposes de variances egales. + """ + [aire, Q, reponse, message] = IndependantVectorsEqualVariance( + vector1 = V1, + vector2 = V2, + tolerance = self.parameters["tolerance"], + ) + logging.info( message ) + answerStudentTest = False + if (aire < (100.*self.parameters["tolerance"])) : + answerStudentTest = False + else: + answerStudentTest = True + return answerStudentTest + + def calculate(self, vector1 = None, vector2 = None, step = None): + """ + Active la formule de calcul + """ + if (vector1 is None) or (vector2 is None) : + raise ValueError("Two vectors must be given to calculate the Student value") + V1 = numpy.array(vector1) + V2 = numpy.array(vector2) + if (V1.size < 1) or (V2.size < 1): + raise ValueError("The given vectors must not be empty") + if V1.size != V2.size: + raise ValueError("The two given vectors must have the same size, or the vector types are incompatible") + value = self.formula( V1, V2 ) + self.store( value = value, step = step) + +# ============================================================================== +if __name__ == "__main__": + print '\n AUTODIAGNOSTIC \n' + + print " Test d'égalite des moyennes au sens de Student pour deux vecteurs" + print " indépendants supposés de variances égales" + print + # + # Initialisation des inputs et appel du diagnostic + # -------------------------------------------------------------------- + tolerance = 0.05 + D = ElementaryDiagnostic("ComputeMeanStudent_IndepVect_EgalVar", parameters = { + "tolerance":tolerance, + }) + # + # Tirage de l'echantillon aleatoire + # -------------------------------------------------------------------- + x1 = numpy.array(([-0.23262176, 1.36065207, 0.32988102, 0.24400551, -0.66765848, -0.19088483, -0.31082575, 0.56849814, 1.21453443, 0.99657516])) + x2 = numpy.array(([-0.23, 1.36, 0.32, 0.24, -0.66, -0.19, -0.31, 0.56, 1.21, 0.99])) + # + # Calcul + # -------------------------------------------------------------------- + D.calculate(x1, x2) + # + if D.valueserie(0) : + print " L'hypothèse d'égalité des moyennes est valide." + print + else : + raise ValueError("The egality of the means is NOT valid") + diff --git a/src/daComposant/daDiagnostics/CompareVarianceFisher.py b/src/daComposant/daDiagnostics/CompareVarianceFisher.py new file mode 100644 index 0000000..0fc3a96 --- /dev/null +++ b/src/daComposant/daDiagnostics/CompareVarianceFisher.py @@ -0,0 +1,119 @@ +#-*-coding:iso-8859-1-*- +# +# Copyright (C) 2008-2009 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +__doc__ = """ + Diagnostic qui compare les variances de 2 vecteurs au sens de Fisher à + l'aide du calcul de la p-value pour le test de Fisher. + - entrée : la tolérance (tolerance) sous forme de paramètres dans le + dictionnaire Par, et les deux vecteurs d'échantillons. + - sortie : le résultat du diagnostic est une réponse booléenne au test : + True si l'égalite des variances est valide au sens du test de Fisher, + False dans le cas contraire +""" +__author__ = "Sophie RICCI - Juillet 2008" + +import sys ; sys.path.insert(0, "../daCore") + +import numpy +import Persistence +from BasicObjects import Diagnostic +from ComputeFisher import ComputeFisher +import logging + +# ============================================================================== +class ElementaryDiagnostic(Diagnostic,Persistence.OneScalar): + """ + Diagnostic qui compare les variances de 2 vecteurs au sens de Fisher à + l'aide du calcul de la p-value pour le test de Fisher. + - entrée : la tolérance (tolerance) sous forme de paramètres dans le + dictionnaire Par, et les deux vecteurs d'échantillons. + - sortie : le résultat du diagnostic est une réponse booléenne au test : + True si l'égalite des variances est valide au sens du test de Fisher, + False dans le cas contraire + """ + def __init__(self, name="", unit="", basetype = None, parameters = {} ): + Diagnostic.__init__(self, name, parameters) + Persistence.OneScalar.__init__( self, name, unit, basetype = bool) + if not self.parameters.has_key("tolerance"): + raise ValueError("A parameter named \"tolerance\" is required.") + + def formula(self, V1, V2): + """ + Effectue le test de Fisher avec la p-value pour 2 vecteurs + """ + [aire, f, reponse, message] = ComputeFisher( + vector1 = V1, + vector2 = V2, + tolerance = self.parameters["tolerance"], + ) + answerKhisquareTest = False + if (aire < (100.*self.parameters["tolerance"])) : + answerKhisquareTest = False + else: + answerKhisquareTest = True + logging.info( message ) + # + return answerKhisquareTest + + def calculate(self, vector1 = None, vector2 = None, step = None): + """ + Active la formule de calcul + """ + if (vector1 is None) or (vector2 is None) : + raise ValueError("Two vectors must be given to calculate the Fisher p-value") + V1 = numpy.array(vector1) + V2 = numpy.array(vector2) + if (V1.size < 1) or (V2.size < 1): + raise ValueError("The given vectors must not be empty") + if V1.size != V2.size: + raise ValueError("The two given vectors must have the same size, or the vector types are incompatible") + # + value = self.formula( V1, V2 ) + # + self.store( value = value, step = step) + +# ============================================================================== +if __name__ == "__main__": + print '\n AUTODIAGNOSTIC \n' + + print " Test d'égalite des variances pour deux vecteurs de taille 10" + print + # + # Initialisation des inputs et appel du diagnostic + # -------------------------------------------------------------------- + tolerance = 0.05 + D = ElementaryDiagnostic("CompareVarianceFisher", parameters = { + "tolerance":tolerance, + }) + # + # Tirage de l'echantillon aleatoire + # -------------------------------------------------------------------- + x1 = numpy.array(([-0.23262176, 1.36065207, 0.32988102, 0.24400551, -0.66765848, -0.19088483, -0.31082575, 0.56849814, 1.21453443, 0.99657516])) + x2 = numpy.array(([-0.23, 1.36, 0.32, 0.24, -0.66, -0.19, -0.31, 0.56, 1.21, 0.99])) + # + # Calcul + # -------------------------------------------------------------------- + D.calculate(x1, x2) + # + if D.valueserie(0) : + print " L'hypothèse d'égalité des deux variances est correcte." + print + else : + raise ValueError("L'hypothèse d'égalité des deux variances est fausse.") diff --git a/src/daComposant/daDiagnostics/ComputeBiais.py b/src/daComposant/daDiagnostics/ComputeBiais.py new file mode 100644 index 0000000..9c05425 --- /dev/null +++ b/src/daComposant/daDiagnostics/ComputeBiais.py @@ -0,0 +1,89 @@ +#-*-coding:iso-8859-1-*- +# +# Copyright (C) 2008-2009 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +__doc__ = """ + Calcul du biais (i.e. la moyenne) à chaque pas. Ce diagnostic très simple + est présent pour rappeller à l'utilisateur de l'assimilation qu'il faut + qu'il vérifie le biais de ses erreurs en particulier. +""" +__author__ = "Sophie RICCI - Aout 2008" + +import sys ; sys.path.insert(0, "../daCore") + +import numpy +import Persistence +from BasicObjects import Diagnostic +from AssimilationStudy import AssimilationStudy + +# ============================================================================== +class ElementaryDiagnostic(Diagnostic,Persistence.OneScalar): + def __init__(self, name = "", unit = "", basetype = None, parameters = {}): + Diagnostic.__init__(self, name, parameters) + Persistence.OneScalar.__init__( self, name, unit, basetype = float ) + + def _formula(self, V): + """ + Calcul du biais, qui est simplement la moyenne du vecteur + """ + biais = V.mean() + # + return biais + + def calculate(self, vector = None, step = None): + """ + Teste les arguments, active la formule de calcul et stocke le résultat + """ + if vector is None: + raise ValueError("One vector must be given to compute biais") + V = numpy.array(vector) + if V.size < 1: + raise ValueError("The given vector must not be empty") + # + value = self._formula( V) + # + self.store( value = value, step = step ) + +#=============================================================================== +if __name__ == "__main__": + print '\n AUTODIAGNOSTIC \n' + # + # Instanciation de l'objet diagnostic + # ----------------------------------- + D = ElementaryDiagnostic("Mon ComputeBiais") + # + # Tirage d un vecteur choisi + # -------------------------- + x = numpy.matrix(([3., 4., 5.])) + print " Le vecteur de type 'matrix' choisi est..:", x + print " Le biais attendu de ce vecteur est......:", x.mean() + # + D.calculate( vector = x) + print " Le biais obtenu de ce vecteur est.......:", D.valueserie(0) + print + # + # Tirage d un vecteur choisi + # -------------------------- + x = numpy.array(range(11)) + print " Le vecteur de type 'array' choisi est...:", x + print " Le biais attendu de ce vecteur est......:", x.mean() + # + D.calculate( vector = x) + print " Le biais obtenu de ce vecteur est.......:", D.valueserie(1) + print diff --git a/src/daComposant/daDiagnostics/ComputeCostFunction.py b/src/daComposant/daDiagnostics/ComputeCostFunction.py new file mode 100644 index 0000000..9504abf --- /dev/null +++ b/src/daComposant/daDiagnostics/ComputeCostFunction.py @@ -0,0 +1,141 @@ +#-*-coding:iso-8859-1-*- +# +# Copyright (C) 2008-2009 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +__doc__ = """ + Calcul de la fonction coût +""" +__author__ = "Sophie RICCI - Octobre 2008" + +import sys ; sys.path.insert(0, "../daCore") + +import numpy +import Persistence +from BasicObjects import Diagnostic +from AssimilationStudy import AssimilationStudy +import logging + +# ============================================================================== +class ElementaryDiagnostic(Diagnostic,Persistence.OneScalar): + def __init__(self, name = "", unit = "", basetype = None, parameters = {}): + Diagnostic.__init__(self, name) + Persistence.OneScalar.__init__( self, name, unit, basetype = float) + + def _formula(self, X, HX, Xb, Y, R, B): + """ + Calcul de la fonction cout + """ + Jb = 1./2. * (X - Xb).T * B.I * (X - Xb) + logging.info( "Partial cost function : Jb = %s"%Jb ) + # + Jo = 1./2. * (Y - HX).T * R.I * (Y - HX) + logging.info( "Partial cost function : Jo = %s"%Jo ) + # + J = Jb + Jo + logging.info( "Total cost function : J = Jo + Jb = %s"%J ) + return J + + def calculate(self, x = None, Hx = None, xb = None, yo = None, R = None, B = None , step = None): + """ + Teste les arguments, active la formule de calcul et stocke le résultat + """ + if (x is None) or (xb is None) or (yo is None) : + raise ValueError("Vectors x, xb and yo must be given to compute J") +# if (type(x) is not float) and (type(x) is not numpy.float64) : +# if (x.size < 1) or (xb.size < 1) or (yo.size < 1): +# raise ValueError("Vectors x, xb and yo must not be empty") + if hasattr(numpy.matrix(x),'A1') : + X = numpy.matrix(x).A1 + if hasattr(numpy.matrix(xb),'A1') : + Xb = numpy.matrix(xb).A1 + if hasattr(numpy.matrix(yo),'A1') : + Y = numpy.matrix(yo).A1 + B = numpy.matrix(B) + R = numpy.matrix(R) + if (Hx is None ) : + raise ValueError("The given vector must be given") +# if (Hx.size < 1) : +# raise ValueError("The given vector must not be empty") + HX = Hx.A1 + if (B is None ) or (R is None ): + raise ValueError("The matrices B and R must be given") +# if (B.size < 1) or (R.size < 1) : +# raise ValueError("The matrices B and R must not be empty") + # + value = self._formula(X, HX, Xb, Y, R, B) + # + self.store( value = value, step = step ) + +#=============================================================================== +if __name__ == "__main__": + print "\nAUTOTEST\n" + # + D = ElementaryDiagnostic("Ma fonction cout") + # + # Vecteur de type array + # --------------------- + x = numpy.array([1., 2.]) + xb = numpy.array([2., 2.]) + yo = numpy.array([5., 6.]) + H = numpy.matrix(numpy.identity(2)) + Hx = H*x + Hx = Hx.T + B = numpy.matrix(numpy.identity(2)) + R = numpy.matrix(numpy.identity(2)) + # + D.calculate( x = x, Hx = Hx, xb = xb, yo = yo, R = R, B = B) + print "Le vecteur x choisi est...:", x + print "L ebauche xb choisie est...:", xb + print "Le vecteur d observation est...:", yo + print "B = ", B + print "R = ", R + print "La fonction cout J vaut ...: %.2e"%D.valueserie(0) + print "La fonction cout J vaut ...: ",D.valueserie(0) + + if (abs(D.valueserie(0) - 16.5) > 1.e-6) : + raise ValueError("The computation of the cost function is NOT correct") + else : + print "The computation of the cost function is OK" + print + # + # float simple + # ------------ + x = 1. + print type(x) + xb = 2. + yo = 5. + H = numpy.matrix(numpy.identity(1)) + Hx = numpy.dot(H,x) + Hx = Hx.T + B = 1. + R = 1. + # + D.calculate( x = x, Hx = Hx, xb = xb, yo = yo, R = R, B = B) + print "Le vecteur x choisi est...:", x + print "L ebauche xb choisie est...:", xb + print "Le vecteur d observation est...:", yo + print "B = ", B + print "R = ", R + print "La fonction cout J vaut ...: %.2e"%D.valueserie(1) + if (abs(D.valueserie(1) - 8.5) > 1.e-6) : + raise ValueError("The computation of the cost function is NOT correct") + else : + print "The computation of the cost function is OK" + print + diff --git a/src/daComposant/daDiagnostics/ComputeCostFunctionLin.py b/src/daComposant/daDiagnostics/ComputeCostFunctionLin.py new file mode 100644 index 0000000..550be82 --- /dev/null +++ b/src/daComposant/daDiagnostics/ComputeCostFunctionLin.py @@ -0,0 +1,119 @@ +#-*-coding:iso-8859-1-*- +# +# Copyright (C) 2008-2009 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +__doc__ = """ + Calcul de la fonction coût avec Hlin + HX = Hxb + Hlin dx +""" +__author__ = "Sophie RICCI - Octobre 2008" + +import sys ; sys.path.insert(0, "../daCore") + +import numpy +import Persistence +from BasicObjects import Diagnostic +from AssimilationStudy import AssimilationStudy +import logging + +# ============================================================================== +class ElementaryDiagnostic(Diagnostic,Persistence.OneScalar): + def __init__(self, name = "", unit = "", basetype = None, parameters = {}): + Diagnostic.__init__(self, name) + Persistence.OneScalar.__init__( self, name, unit, basetype = float) + self.__name = str( name ) + + def _formula(self, X = None, dX = None, Hlin = None, Xb=None, HXb = None, Y=None, R=None, B=None): + + """ + Calcul de la fonction cout + """ + HX = HXb + Hlin.T * dX + if hasattr(HX, 'A1') : + HX = HX.A1 + # + Jb = 1./2. * (X - Xb).T * B.I * (X - Xb) + logging.info( "Partial cost function : Jb = %s"%Jb ) + # + Jo = 1./2. * (Y - HX).T * R.I * (Y - HX) + logging.info( "Partial cost function : Jo = %s"%Jo ) + # + J = Jb + Jo + logging.info( "Total cost function : J = Jo + Jb = %s"%J ) + return J + + def calculate(self, x = None, dx = None, Hlin = None, xb = None, Hxb = None, yo = None, R = None, B = None , step = None): + """ + Teste les arguments, active la formule de calcul et stocke le résultat + """ + if (x is None) or (xb is None) or (yo is None) or (dx is None): + raise ValueError("Vectors x, dx, xb and yo must be given to compute J") + dX = dx + if hasattr(numpy.matrix(x), 'A1') : + X = numpy.matrix(x).A1 + if hasattr(numpy.matrix(xb), 'A1') : + Xb = numpy.matrix(xb).A1 + if hasattr(numpy.matrix(yo), 'A1') : + Y = numpy.matrix(yo).A1 + B = numpy.matrix(B) + R = numpy.matrix(R) + if (Hlin is None ) : + raise ValueError("HlinT vector must be given") + if (Hxb is None ) : + raise ValueError("The given vector must be given") + HXb = Hxb + if (B is None ) or (R is None ): + raise ValueError("The matrices B and R must be given") + # + value = self._formula(X, dX, Hlin, Xb, HXb, Y, R, B) + # + self.store( value = value, step = step ) + +#=============================================================================== +if __name__ == "__main__": + print "\nAUTOTEST\n" + # + D = ElementaryDiagnostic("Ma fonction cout") + # + # Vecteur de type array + # --------------------- + x = numpy.array([1., 2.]) + dx = numpy.array([0.1, 0.2]) + xb = numpy.array([2., 2.]) + yo = numpy.array([5., 6.]) + Hlin = numpy.matrix(numpy.identity(2)) + Hxb = Hlin *xb + Hxb = Hxb.T + Hxb = Hxb.A1 + B = numpy.matrix(numpy.identity(2)) + R = numpy.matrix(numpy.identity(2)) + # + D.calculate( x = x, dx = dx, Hlin = Hlin, xb = xb, Hxb = Hxb, yo = yo, R = R, B = B) + print "Le vecteur x choisi est...:", x + print "L ebauche xb choisie est...:", xb + print "Le vecteur d observation est...:", yo + print "B = ", B + print "R = ", R + print "La fonction cout J vaut ...: %.2e"%D.valueserie(0) + # + if (abs(D.valueserie(0) - 11.925) > 1.e-6) : + raise ValueError("The computation of the cost function is NOT correct") + else : + print "The computation of the cost function is OK" + print diff --git a/src/daComposant/daDiagnostics/ComputeVariance.py b/src/daComposant/daDiagnostics/ComputeVariance.py new file mode 100644 index 0000000..22ae8e6 --- /dev/null +++ b/src/daComposant/daDiagnostics/ComputeVariance.py @@ -0,0 +1,90 @@ +#-*-coding:iso-8859-1-*- +# +# Copyright (C) 2008-2009 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +__doc__ = """ + Calcul de la variance d'un vecteur à chaque pas. Ce diagnostic très simple + est présent pour rappeller à l'utilisateur de l'assimilation qu'il faut + qu'il vérifie les variances de ses écarts en particulier. +""" +__author__ = "Jean-Philippe ARGAUD - Septembre 2008" + +import sys ; sys.path.insert(0, "../daCore") + +import numpy +import Persistence +from BasicObjects import Diagnostic +from AssimilationStudy import AssimilationStudy + +# ============================================================================== +class ElementaryDiagnostic(Diagnostic,Persistence.OneScalar): + def __init__(self, name = "", unit = "", basetype = None, parameters = {}): + Diagnostic.__init__(self, name, parameters) + Persistence.OneScalar.__init__( self, name, unit, basetype = float) + + def _formula(self, V): + """ + Calcul de la variance du vecteur en argument. Elle est faite avec une + division par la taille du vecteur. + """ + variance = V.var() + # + return variance + + def calculate(self, vector = None, step = None): + """ + Teste les arguments, active la formule de calcul et stocke le résultat + """ + if vector is None: + raise ValueError("One vector must be given to compute biais") + V = numpy.array(vector) + if V.size < 1: + raise ValueError("The given vector must not be empty") + # + value = self._formula( V) + # + self.store( value = value, step = step ) + +#=============================================================================== +if __name__ == "__main__": + print '\n AUTODIAGNOSTIC \n' + # + D = ElementaryDiagnostic("Ma variance") + # + # Vecteur de type matrix + # ---------------------- + x = numpy.matrix(([3., 4., 5.])) + print " Le vecteur de type 'matrix' choisi est..:", x + print " Le moyenne de ce vecteur est............:", x.mean() + print " La variance attendue de ce vecteur est..:", x.var() + # + D.calculate( vector = x) + print " La variance obtenue de ce vecteur est...:", D.valueserie(0) + print + # + # Vecteur de type array + # --------------------- + x = numpy.array(range(11)) + print " Le vecteur de type 'array' choisi est...:", x + print " Le moyenne de ce vecteur est............:", x.mean() + print " La variance attendue de ce vecteur est..:", x.var() + # + D.calculate( vector = x) + print " La variance obtenue de ce vecteur est...:", D.valueserie(1) + print diff --git a/src/daComposant/daDiagnostics/GaussianAdequation.py b/src/daComposant/daDiagnostics/GaussianAdequation.py new file mode 100644 index 0000000..c027659 --- /dev/null +++ b/src/daComposant/daDiagnostics/GaussianAdequation.py @@ -0,0 +1,159 @@ +#-*-coding:iso-8859-1-*- +# +# Copyright (C) 2008-2009 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +__doc__ = """ + Diagnostic qui effectue le test du Khi2 pour juger de l'adéquation entre + la distribution d'un échantillon et une distribution gaussienne dont la + moyenne et l'écart-type sont calculés sur l'échantillon. + En input : la tolerance(tolerance) et le nombre de classes(nbclasse) + En output : Le resultat du diagnostic est une reponse booleenne au test : + True si l adequation a une distribution gaussienne est valide + au sens du test du Khi2, + False dans le cas contraire. +""" +__author__ = "Sophie RICCI - Juillet 2008" + +import sys ; sys.path.insert(0, "../daCore") + +import numpy +from numpy import random +import Persistence +from BasicObjects import Diagnostic +from ComputeKhi2 import ComputeKhi2_Gauss +import logging + +# ============================================================================== +class ElementaryDiagnostic(Diagnostic,Persistence.OneScalar): + """ + """ + def __init__(self, name="", unit="", basetype = None, parameters = {} ): + Diagnostic.__init__(self, name, parameters) + Persistence.OneScalar.__init__( self, name, unit, basetype = bool) + for key in ["tolerance", "dxclasse", "nbclasses"]: + if not self.parameters.has_key(key): + raise ValueError("A parameter named \"%s\" is required."%key) + + def formula(self, V): + """ + Effectue le calcul de la p-value pour un vecteur et une distribution + gaussienne et un nombre de classes donne en parametre du diagnostic. + """ + + [vectclasse, eftho, efobs, valeurKhi2, areaKhi2, message] = ComputeKhi2_Gauss( + vectorV = V, + dx = self.parameters["dxclasse"], + nbclasses = self.parameters["nbclasses"], + SuppressEmptyClasses = True) + + + logging.info( message ) + logging.info( "(si <%.2f %s on refuse effectivement l'adéquation)"%(100.*self.parameters["tolerance"],"%") ) + logging.info("vecteur des classes=%s"%numpy.size(vectclasse) ) + logging.info("valeurKhi2=%s"%valeurKhi2) + logging.info("areaKhi2=%s"%areaKhi2) + logging.info("tolerance=%s"%self.parameters["tolerance"]) + + if (areaKhi2 < (100.*self.parameters["tolerance"])) : + answerKhisquareTest = False + else: + answerKhisquareTest = True + logging.info( "La réponse au test est donc est %s"%answerKhisquareTest ) + return answerKhisquareTest + + def calculate(self, vector = None, step = None): + """ + Active la formule de calcul + """ + if vector is None: + raise ValueError("One vector must be given to calculate the Khi2 test") + V = numpy.array(vector) + if V.size < 1: + raise ValueError("The given vector must not be empty") + # + value = self.formula( V ) + # + self.store( value = value, step = step) + +# ============================================================================== +if __name__ == "__main__": + print "\n AUTODIAGNOSTIC \n" + + print " Test d adequation du khi-2 a une gaussienne pour un vecteur x" + print " connu de taille 1000, issu d'une distribution gaussienne normale" + print " en fixant la largeur des classes" + print + # + # Initialisation des inputs et appel du diagnostic + # ------------------------------------------------ + tolerance = 0.05 + dxclasse = 0.1 + D = ElementaryDiagnostic("AdequationGaussKhi2", parameters = { + "tolerance":tolerance, + "dxclasse":dxclasse, + "nbclasses":None, + }) + # + # Tirage de l'echantillon aleatoire + # --------------------------------- + numpy.random.seed(2490) + x = random.normal(50.,1.5,1000) + # + # Calcul + # ------ + D.calculate(x) + # + if D.valueserie(0) : + print " L'adequation a une distribution gaussienne est valide." + print + else : + raise ValueError("L'adéquation a une distribution gaussienne n'est pas valide.") + + + print " Test d adequation du khi-2 a une gaussienne pour u:n vecteur x" + print " connu de taille 1000, issu d'une distribution gaussienne normale" + print " en fixant le nombre de classes" + print + # + # Initialisation des inputs et appel du diagnostic + # ------------------------------------------------ + tolerance = 0.05 + nbclasses = 70. + D = ElementaryDiagnostic("AdequationGaussKhi2", parameters = { + "tolerance":tolerance, + "dxclasse":None, + "nbclasses":nbclasses + }) + # + # Tirage de l'echantillon aleatoire + # --------------------------------- + numpy.random.seed(2490) + x = random.normal(50.,1.5,1000) + # + # Calcul + # ------ + D.calculate(x) + # + if D.valueserie(0) : + print " L'adequation a une distribution gaussienne est valide." + print + else : + raise ValueError("L'adequation a une distribution gaussienne n'est pas valide.") + + diff --git a/src/daComposant/daDiagnostics/HLinearity.py b/src/daComposant/daDiagnostics/HLinearity.py new file mode 100644 index 0000000..9509de6 --- /dev/null +++ b/src/daComposant/daDiagnostics/HLinearity.py @@ -0,0 +1,143 @@ +#-*-coding:iso-8859-1-*- +# +# Copyright (C) 2008-2009 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +__doc__ = """ + Diagnotic de test sur la validité de l'hypothèse de linéarité de l'opérateur + H entre xp et xm + + Pour calculer Hlin on utilise un schéma différences finies centrées 2 + points. On définit un dxparam tel que : + xp = xb + dxparam + et + xm = xb - dxparam + On calcule Hxp et Hxm pour obtenir Hlin. Hlin est utilise dans le Blue pour + caler un paramêtre. La question importante est de choisir un dxparam pas + trop grand. + + On veut vérifier ici que l'hypothèse de linéarite du modèle par rapport au + paramêtre est valide sur l'intervalle du paramêtre [xm, xp]. Pour cela on + s'assure que l'on peut retrouver la valeur Hxb par les développemenents de + Taylor en xp et xm. Ainsi on calcule 2 estimations de Hxb, l'une à partir de + Hxp (notee Hx1) et l'autre à partir de Hxm (notee Hx2), que l'on compare à + la valeur calculée de Hxb. On s'intèresse ensuite a la distance entre Hxb et + ses estimés Hx1 et Hx2. Si la distance est inférieure a un seuil de + tolerance, l hypothese est valide. +""" +__author__ = "Sophie RICCI - Septembre 2008" + +import sys ; sys.path.insert(0, "../daCore") + +import numpy +import Persistence +from BasicObjects import Diagnostic +from RMS import ElementaryDiagnostic as RMS +from AssimilationStudy import AssimilationStudy + +# ============================================================================== +class ElementaryDiagnostic(Diagnostic,Persistence.OneScalar): + def __init__(self, name="", unit="", basetype = None, parameters = {} ): + Diagnostic.__init__(self, name, parameters) + Persistence.OneScalar.__init__( self, name, unit, basetype = bool) + if not self.parameters.has_key("tolerance"): + raise ValueError("A parameter named \"tolerance\" is required.") + + def formula(self, H, dxparam, Hxp, Hxm, Hx): + """ + Test sur la validite de l hypothese de linearite de H entre xp et xm + """ + dimension = numpy.size(Hx) + # + # Reconstruit les valeurs Hx1 et Hx2 de Hx a partir de Hxm et Hxp + # --------------------------------------------------------------- + Hx1 = Hxm + H.T * dxparam + Hx2 = Hxp - H.T * dxparam + # + # Calcul de l'ecart entre Hx1 et Hx et entre Hx2 et Hx + # ---------------------------------------------------- + ADD = AssimilationStudy() + ADD.setDiagnostic("RMS", + name = "Calcul de la RMS entre Hx1 et Hx et entre Hx2 et Hx") + RMS = ADD.get("Calcul de la RMS entre Hx1 et Hx et entre Hx2 et Hx") + RMS.calculate(Hx1,Hx) + std1 = RMS.valueserie(0) + RMS.calculate(Hx2,Hx) + std2 = RMS.valueserie(1) + # + # Normalisation des écarts par Hx pour comparer a un pourcentage + # -------------------------------------------------------------- + RMS.calculate(Hx,Hx-Hx) + std = RMS.valueserie(2) + err1=std1/std + err2=std2/std + # + # Comparaison + # ----------- + if ( (err1 < self.parameters["tolerance"]) and (err2 < self.parameters["tolerance"]) ): + reponse = True + else: + reponse = False + return reponse + + def calculate(self, Hlin = None, deltaparam = None, Hxp = None, Hxm = None, Hx = None, step = None): + """ + Arguments : + - Hlin : Operateur d obsevation lineaire + - deltaparam : pas sur le parametre param + - Hxp : calcul en xp = xb + deltaparam + - Hxm : calcul en xm = xb - deltaparam + - Hx : calcul en x (generalement xb) + """ + value = self.formula( Hlin, deltaparam, Hxp, Hxm, Hx ) + # + self.store( value = value, step = step) + +#=============================================================================== +if __name__ == "__main__": + print '\n AUTODIAGNOSTIC \n' + + print " Diagnotic de test sur la validité de l'hypothèse de linéarité de" + print " l'opérateur H entre xp et xm." + print + # + dimension = 3 + # + # Définition des données + # ---------------------- + Hx = numpy.array(([ 2., 4., 6.])) + Hxp = numpy.array(([ 3., 5., 7.])) + Hxm = numpy.array(([ 1., 3., 5.])) + H = (Hxp - Hxm)/(2.) + dxparam = 1. + # + # Instanciation de l'objet diagnostic + # ----------------------------------- + D = ElementaryDiagnostic("Mon TestHlin", parameters = {"tolerance": 0.1}) + # + # Calcul + # ------ + D.calculate( Hlin = H, deltaparam = dxparam, Hxp = Hxp, Hxm = Hxm, Hx = Hx) + + # Validation du calcul + # -------------------- + if not D.valueserie(0) : + raise ValueError("La linearisation de H autour de x entre xm et xp est fausse pour ce cas test lineaire") + else : + print " La linéarisation de H autour de x entre xm et xp est valide pour ce cas-test linéaire." + print diff --git a/src/daComposant/daDiagnostics/HomogeneiteKhi2.py b/src/daComposant/daDiagnostics/HomogeneiteKhi2.py new file mode 100644 index 0000000..acb2413 --- /dev/null +++ b/src/daComposant/daDiagnostics/HomogeneiteKhi2.py @@ -0,0 +1,121 @@ +#-*-coding:iso-8859-1-*- +# +# Copyright (C) 2008-2009 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +__doc__ = """ + Diagnostic qui effectue le test du Khi2 pour juger de l'homogénéite entre + les distributions de 2 vecteurs quelconques. + - entrée : la tolerance (tolerance) et le nombre de classes (nbclasse), + sous forme de paramètres dans le dictionnaire Par + - sortie : le resultat du diagnostic est une reponse booleenne au test : + True si l homogeneite est valide au sens du test du Khi2, + False dans le cas contraire. +""" +__author__ = "Sophie RICCI - Juillet 2008" + +import sys ; sys.path.insert(0, "../daCore") + +import numpy +from numpy import random + +import Persistence +from BasicObjects import Diagnostic +from ComputeKhi2 import ComputeKhi2_Homogen +import logging + +# ============================================================================== +class ElementaryDiagnostic(Diagnostic,Persistence.OneScalar): + def __init__(self, name="", unit="", basetype = None, parameters = {} ): + Diagnostic.__init__(self, name, parameters) + Persistence.OneScalar.__init__( self, name, unit, basetype = bool ) + for key in ["tolerance", "dxclasse", "nbclasses"]: + if not self.parameters.has_key(key): + raise ValueError("A parameter named \"%s\" is required."%key) + + def _formula(self, V1, V2): + """ + Effectue le calcul de la p-value pour deux vecteurs et un nombre de + classes donne en parametre du diagnostic. + """ + [classes, eftheo, efobs, valeurKhi2, areaKhi2, message] = ComputeKhi2_Homogen( + vectorV1 = V1, + vectorV2 = V2, + dx = self.parameters["dxclasse"], + nbclasses = self.parameters["nbclasses"], + SuppressEmptyClasses = True) + # + logging.info( message ) + logging.info( "(si <%.2f %s on refuse effectivement l'homogeneite)"%(100.*self.parameters["tolerance"],"%") ) + # + answerKhisquareTest = False + if (areaKhi2 < (100.*self.parameters["tolerance"])) : + answerKhisquareTest = False + else: + answerKhisquareTest = True + # + return answerKhisquareTest + + def calculate(self, vector1 = None, vector2 = None, step = None): + """ + Active la formule de calcul + """ + if (vector1 is None) or (vector2 is None) : + raise ValueError("Two vectors must be given to calculate the Khi2 value") + V1 = numpy.array(vector1) + V2 = numpy.array(vector2) + if (V1.size < 1) or (V2.size < 1): + raise ValueError("The given vectors must not be empty") + if V1.size != V2.size: + raise ValueError("The two given vectors must have the same size") + # + value = self._formula( V1, V2 ) + # + self.store( value = value, step = step ) + +# ============================================================================== +if __name__ == "__main__": + print "\n AUTODIAGNOSTIC \n" + + print " Test d'homogeneite du Khi-2 pour deux vecteurs de taille 10," + print " issus d'une distribution gaussienne normale" + print + # + # Initialisation des inputs et appel du diagnostic + # -------------------------------------------------------------------- + tolerance = 0.05 + dxclasse = 0.5 + D = ElementaryDiagnostic("HomogeneiteKhi2", parameters = { + "tolerance":tolerance, + "dxclasse":dxclasse, + "nbclasses":None, + }) + # + # Tirage de l'echantillon aleatoire + # -------------------------------------------------------------------- + numpy.random.seed(4000) + x1 = random.normal(50.,1.5,10000) + numpy.random.seed(2490) + x2 = random.normal(50.,1.5,10000) + # + # Calcul + # -------------------------------------------------------------------- + D.calculate(x1, x2) + # + print " La reponse du test est \"%s\" pour une tolerance de %.2e et une largeur de classe de %.2e "%(D.valueserie(0), tolerance, dxclasse) + print diff --git a/src/daComposant/daDiagnostics/PlotVector.py b/src/daComposant/daDiagnostics/PlotVector.py new file mode 100644 index 0000000..97b3372 --- /dev/null +++ b/src/daComposant/daDiagnostics/PlotVector.py @@ -0,0 +1,153 @@ +#-*-coding:iso-8859-1-*- +# +# Copyright (C) 2008-2009 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +__doc__ = """ + Classe pour tracer simplement un vecteur à chaque pas +""" +__author__ = "Jean-Philippe ARGAUD - Juillet 2008" + +import sys ; sys.path.insert(0, "../daCore") + +import os.path +import numpy +from BasicObjects import Diagnostic + +# ============================================================================== +class ElementaryDiagnostic(Diagnostic): + def __init__(self, name = "", unit = "", basetype = None, parameters = {}): + Diagnostic.__init__(self, name, parameters) + try: + import Gnuplot + self.__gnuplot = Gnuplot + except: + raise ImportError("The Gnuplot module is required to plot the vector") + + def _formula(self, + Vector, Steps, + title, xlabel, ylabel, ltitle, + geometry, + filename, + persist, + pause ): + """ + Trace en gnuplot le vecteur Vector, avec une légende générale, en X et + en Y + """ + if persist: + self.__gnuplot.GnuplotOpts.gnuplot_command = 'gnuplot -persist -geometry '+geometry + else: + self.__gnuplot.GnuplotOpts.gnuplot_command = 'gnuplot -geometry '+geometry + # + self.__g = self.__gnuplot.Gnuplot() # persist=1 + self.__g('set terminal '+self.__gnuplot.GnuplotOpts.default_term) + self.__g('set style data lines') + self.__g('set grid') + self.__g('set autoscale') + self.__g('set title "'+title +'"') + self.__g('set xlabel "'+xlabel+'"') + self.__g('set ylabel "'+ylabel+'"') + self.__g.plot( self.__gnuplot.Data( Steps, Vector, title=ltitle ) ) + if filename != "": + self.__g.hardcopy(filename=filename, color=1) + if pause: + raw_input('Please press return to continue...\n') + # + return 1 + + def calculate(self, vector = None, steps = None, + title = "", xlabel = "", ylabel = "", ltitle = None, + geometry = "600x400", + filename = "", + persist = False, + pause = True ): + """ + Arguments : + - vector : le vecteur à tracer, en liste ou en numpy.array + - steps : liste unique des pas de l'axe des X, ou None si c'est + la numérotation par défaut + - title : titre général du dessin + - xlabel : label de l'axe des X + - ylabel : label de l'axe des Y + - ltitle : titre associé au vecteur tracé + - geometry : taille en pixels de la fenêtre et position du coin haut + gauche, au format X11 : LxH+X+Y (défaut : 600x400) + - filename : nom de fichier Postscript pour une sauvegarde à 1 pas + Attention, il faut changer le nom à l'appel pour + plusieurs pas de sauvegarde + - persist : booléen indiquant que la fenêtre affichée sera + conservée lors du passage au dessin suivant + Par défaut, persist = False + - pause : booléen indiquant une pause après chaque tracé, et + attendant un Return + Par défaut, pause = True + """ + if vector is None: + raise ValueError("One vector must be given to plot it.") + if ltitle is None: + ltitle = "" + Vector = numpy.array(vector) + if Vector.size < 1: + raise ValueError("The given vector must not be empty") + if steps is None: + Steps = range(len( vector )) + elif not ( type(steps) is type([]) or type(steps) is not type(numpy.array([])) ): + raise ValueError("The steps must be given as a list/tuple.") + else: + Steps = list(steps) + if os.path.isfile(filename): + raise ValueError("Error: a file with this name \"%s\" already exists."%filename) + # + value = self._formula( + Vector = Vector, + Steps = Steps, + title = str(title).encode('ascii','replace'), + xlabel = str(xlabel).encode('ascii','replace'), + ylabel = str(ylabel).encode('ascii','replace'), + ltitle = str(ltitle), + geometry = str(geometry), + filename = str(filename), + persist = bool(persist), + pause = bool(pause) ) + +# ============================================================================== +if __name__ == "__main__": + print '\n AUTODIAGNOSTIC \n' + + D = ElementaryDiagnostic("Mon Plot") + + vect = [1, 2, 1, 2, 1] + D.calculate(vect, title = "Vecteur 1", xlabel = "Axe X", ylabel = "Axe Y" ) + vect = [1, 3, 1, 3, 1] + D.calculate(vect, title = "Vecteur 2", filename = "vecteur.ps") + vect = [1, 1, 1, 1, 1] + D.calculate(vect, title = "Vecteur 3") + vect = [0.29, 0.97, 0.73, 0.01, 0.20] + D.calculate(vect, title = "Vecteur 4") + vect = [-0.23262176, 1.36065207, 0.32988102, 0.24400551, -0.66765848, -0.19088483, -0.31082575, 0.56849814, 1.21453443, 0.99657516] + D.calculate(vect, title = "Vecteur 5") + vect = [0.29, 0.97, 0.73, 0.01, 0.20] + D.calculate(vect, title = "Vecteur 6 affiche avec une autre geometrie et position", geometry="800x200+50+50") + vect = 100*[0.29, 0.97, 0.73, 0.01, 0.20] + D.calculate(vect, title = "Vecteur 7 : long construit par repetition") + vect = [0.29, 0.97, 0.73, 0.01, 0.20] + D.calculate(vect, title = "Vecteur 8", ltitle = "Vecteur 8") + temps = [0.1,0.2,0.3,0.4,0.5] + D.calculate(vect, temps, title = "Vecteur 8 avec axe du temps modifie") + print diff --git a/src/daComposant/daDiagnostics/PlotVectors.py b/src/daComposant/daDiagnostics/PlotVectors.py new file mode 100644 index 0000000..219519e --- /dev/null +++ b/src/daComposant/daDiagnostics/PlotVectors.py @@ -0,0 +1,157 @@ +#-*-coding:iso-8859-1-*- +# +# Copyright (C) 2008-2009 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +__doc__ = """ + Classe pour tracer simplement une liste de vecteurs à chaque pas +""" +__author__ = "Jean-Philippe ARGAUD - Septembre 2008" + +import sys ; sys.path.insert(0, "../daCore") + +import os.path +import numpy +from BasicObjects import Diagnostic + +# ============================================================================== +class ElementaryDiagnostic(Diagnostic): + def __init__(self, name = "", unit = "", basetype = None, parameters = {}): + Diagnostic.__init__(self, name, parameters) + try: + import Gnuplot + self.__gnuplot = Gnuplot + except: + raise ImportError("The Gnuplot module is required to plot the vector") + + def _formula(self, + Vector, Steps, + title, xlabel, ylabel, ltitle, + geometry, + filename, + persist, + pause ): + """ + Trace en gnuplot chaque vecteur de la liste Vector, avec une légende + générale, en X et en Y + """ + if persist: + self.__gnuplot.GnuplotOpts.gnuplot_command = 'gnuplot -persist -geometry '+geometry + else: + self.__gnuplot.GnuplotOpts.gnuplot_command = 'gnuplot -geometry '+geometry + # + self.__g = self.__gnuplot.Gnuplot() # persist=1 + self.__g('set terminal '+self.__gnuplot.GnuplotOpts.default_term) + self.__g('set style data lines') + self.__g('set grid') + self.__g('set autoscale') + self.__g('set title "'+title +'"') + self.__g('set xlabel "'+xlabel+'"') + self.__g('set ylabel "'+ylabel+'"') + self.__g.plot( self.__gnuplot.Data( Steps, Vector.pop(0), title=ltitle.pop(0) ) ) + for vector in Vector: + self.__g.replot( self.__gnuplot.Data( Steps, vector, title=ltitle.pop(0) ) ) + if filename != "": + self.__g.hardcopy(filename=filename, color=1) + if pause: + raw_input('Please press return to continue...\n') + # + return 1 + + def calculate(self, vector = None, steps = None, + title = "", xlabel = "", ylabel = "", ltitle = None, + geometry = "600x400", + filename = "", + persist = False, + pause = True ): + """ + Arguments : + - vector : liste des vecteurs à tracer, chacun étant en liste ou + en numpy.array + - steps : liste unique des pas, ou None si c'est la numérotation + par défaut + - title : titre général du dessin + - xlabel : label de l'axe des X + - ylabel : label de l'axe des Y + - ltitle : liste des titres associés à chaque vecteur, dans le + même ordre que les vecteurs eux-mêmes + - geometry : taille en pixels de la fenêtre et position du coin haut + gauche, au format X11 : LxH+X+Y (défaut : 600x400) + - filename : nom de fichier Postscript pour une sauvegarde à 1 pas + Attention, il faut changer le nom à l'appel pour + plusieurs pas de sauvegarde + - persist : booléen indiquant que la fenêtre affichée sera + conservée lors du passage au dessin suivant + Par défaut, persist = False + - pause : booléen indiquant une pause après chaque tracé, et + attendant un Return + Par défaut, pause = True + """ + if vector is None: + raise ValueError("One vector must be given to plot it.") + if type(vector) is not type([]) and type(vector) is not type(()): + raise ValueError("The vector(s) must be given as a list/tuple.") + if ltitle is None or len(ltitle) != len(vector): + ltitle = ["" for i in range(len(vector))] + VectorList = [] + for onevector in vector: + VectorList.append( numpy.array( onevector ) ) + if VectorList[-1].size < 1: + raise ValueError("Each given vector must not be empty.") + if steps is None: + Steps = range(len(vector[0])) + elif not ( type(steps) is type([]) or type(steps) is not type(numpy.array([])) ): + raise ValueError("The steps must be given as a list/tuple.") + else: + Steps = list(steps) + if os.path.isfile(filename): + raise ValueError("Error: a file with this name \"%s\" already exists."%filename) + # + value = self._formula( + Vector = VectorList, + Steps = Steps, + title = str(title).encode('ascii','replace'), + xlabel = str(xlabel).encode('ascii','replace'), + ylabel = str(ylabel).encode('ascii','replace'), + ltitle = [str(lt) for lt in ltitle], + geometry = str(geometry), + filename = str(filename), + persist = bool(persist), + pause = bool(pause), + ) + +# ============================================================================== +if __name__ == "__main__": + print '\n AUTODIAGNOSTIC \n' + + D = ElementaryDiagnostic("Mon Plot") + + vect1 = [1, 2, 1, 2, 1] + D.calculate([vect1,], title = "Vecteur 1", xlabel = "Axe X", ylabel = "Axe Y" ) + vect2 = [1, 3, 1, 3, 1] + D.calculate([vect1,vect2], title = "Vecteurs 1 et 2", filename = "liste_de_vecteurs.ps") + vect3 = [-1, 1, -1, 1, -1] + D.calculate((vect1,vect2,vect3), title = "Vecteurs 1 a 3") + vect4 = 100*[0.29, 0.97, 0.73, 0.01, 0.20] + D.calculate([vect4,], title = "Vecteur 4 : long construit par repetition") + D.calculate( + (vect1,vect2,vect3), + [0.1,0.2,0.3,0.4,0.5], + title = "Vecteurs 1 a 3, temps modifie", + ltitle = ["Vecteur 1","Vecteur 2","Vecteur 3"]) + print diff --git a/src/daComposant/daDiagnostics/RMS.py b/src/daComposant/daDiagnostics/RMS.py new file mode 100644 index 0000000..9ca170f --- /dev/null +++ b/src/daComposant/daDiagnostics/RMS.py @@ -0,0 +1,92 @@ +#-*-coding:iso-8859-1-*- +# +# Copyright (C) 2008-2009 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +__doc__ = """ + Calcul d'une RMS +""" +__author__ = "Jean-Philippe ARGAUD - Juillet 2008" + +import sys ; sys.path.insert(0, "../daCore") + +import math +import numpy +import Persistence +from BasicObjects import Diagnostic + +# ============================================================================== +class ElementaryDiagnostic(Diagnostic,Persistence.OneScalar): + def __init__(self, name = "", unit = "", basetype = None, parameters = {}): + Diagnostic.__init__(self, name, parameters) + Persistence.OneScalar.__init__( self, name, unit, basetype = float) + + def _formula(self, V1, V2): + """ + Fait un écart RMS entre deux vecteurs V1 et V2 + """ + rms = math.sqrt( ((V2 - V1)**2).sum() / float(V1.size) ) + # + return rms + + def calculate(self, vector1 = None, vector2 = None, step = None): + """ + Teste les arguments, active la formule de calcul et stocke le résultat + """ + if vector1 is None or vector2 is None: + raise ValueError("Two vectors must be given to calculate their RMS") + V1 = numpy.array(vector1) + V2 = numpy.array(vector2) + if V1.size < 1 or V2.size < 1: + raise ValueError("The given vectors must not be empty") + if V1.size != V2.size: + raise ValueError("The two given vectors must have the same size") + # + value = self._formula( V1, V2 ) + # + self.store( value = value, step = step ) + +# ============================================================================== +if __name__ == "__main__": + print '\n AUTODIAGNOSTIC \n' + + D = ElementaryDiagnostic("Ma RMS") + + vect1 = [1, 2, 1, 2, 1] + vect2 = [2, 1, 2, 1, 2] + D.calculate(vect1,vect2) + vect1 = [1, 3, 1, 3, 1] + vect2 = [2, 2, 2, 2, 2] + D.calculate(vect1,vect2) + vect1 = [1, 1, 1, 1, 1] + vect2 = [2, 2, 2, 2, 2] + D.calculate(vect1,vect2) + vect1 = [1, 1, 1, 1, 1] + vect2 = [4, -2, 4, -2, -2] + D.calculate(vect1,vect2) + vect1 = [0.29, 0.97, 0.73, 0.01, 0.20] + vect2 = [0.92, 0.86, 0.11, 0.72, 0.54] + D.calculate(vect1,vect2) + vect1 = [-0.23262176, 1.36065207, 0.32988102, 0.24400551, -0.66765848, -0.19088483, -0.31082575, 0.56849814, 1.21453443, 0.99657516] + vect2 = [0,0,0,0,0,0,0,0,0,0] + D.calculate(vect1,vect2) + print " Les valeurs de RMS attendues sont les suivantes : [1.0, 1.0, 1.0, 3.0, 0.53162016515553656, 0.73784217096601323]" + print " Les RMS obtenues................................:", D.valueserie() + print " La moyenne......................................:", D.stepmean() + print + diff --git a/src/daComposant/daDiagnostics/ReduceBiais.py b/src/daComposant/daDiagnostics/ReduceBiais.py new file mode 100644 index 0000000..31ebc18 --- /dev/null +++ b/src/daComposant/daDiagnostics/ReduceBiais.py @@ -0,0 +1,111 @@ +#-*-coding:iso-8859-1-*- +# +# Copyright (C) 2008-2009 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +__doc__ = """ + Diagnostic sur la reduction du biais lors de l'analyse +""" +__author__ = "Sophie RICCI - Aout 2008" + +import sys ; sys.path.insert(0, "../daCore") + +import numpy +import Persistence +from BasicObjects import Diagnostic +from AssimilationStudy import AssimilationStudy + +# ============================================================================== +class ElementaryDiagnostic(Diagnostic,Persistence.OneScalar): + def __init__(self, name = "", unit = "", basetype = None, parameters = {}): + Diagnostic.__init__(self, name, parameters) + Persistence.OneScalar.__init__( self, name, unit, basetype = bool) + + def _formula(self, V1, V2): + """ + Vérification de la reduction du biais entre OMB et OMA lors de l'analyse + """ + biaisOMB = V1.mean() + biaisOMA = V2.mean() + # + if biaisOMA > biaisOMB: + reducebiais = False + else : + reducebiais = True + # + return reducebiais + + def calculate(self, vectorOMB = None, vectorOMA = None, step = None): + """ + Teste les arguments, active la formule de calcul et stocke le résultat + Arguments : + - vectorOMB : vecteur d'écart entre les observations et l'ébauche + - vectorOMA : vecteur d'écart entre les observations et l'analyse + """ + if ( (vectorOMB is None) or (vectorOMA is None) ): + raise ValueError("Two vectors must be given to test the reduction of the biais after analysis") + V1 = numpy.array(vectorOMB) + V2 = numpy.array(vectorOMA) + if V1.size < 1 or V2.size < 1: + raise ValueError("The given vectors must not be empty") + if V1.size != V2.size: + raise ValueError("The two given vectors must have the same size") + # + value = self._formula( V1, V2 ) + # + self.store( value = value, step = step ) + +#=============================================================================== +if __name__ == "__main__": + print '\n AUTODIAGNOSTIC \n' + # + # Instanciation de l'objet diagnostic + # ----------------------------------- + D = ElementaryDiagnostic("Mon ReduceBiais") + # + # Tirage des 2 vecteurs choisis + # ------------------------------- + x1 = numpy.matrix(([3. , 4., 5. ])) + x2 = numpy.matrix(([1.5, 2., 2.5])) + print " L'écart entre les observations et l'ébauche est OMB :", x1 + print " La moyenne de OMB (i.e. le biais) est de............:", x1.mean() + print " L'écart entre les observations et l'analyse est OMA :", x2 + print " La moyenne de OMA (i.e. le biais) est de............:", x2.mean() + # + D.calculate( vectorOMB = x1, vectorOMA = x2) + if not D.valueserie(0) : + print " Résultat : l'analyse NE RÉDUIT PAS le biais" + else : + print " Résultat : l'analyse RÉDUIT le biais" + print + # + # Tirage des 2 vecteurs choisis + # ------------------------------- + x1 = numpy.matrix(range(-5,6)) + x2 = numpy.array(range(11)) + print " L'écart entre les observations et l'ébauche est OMB :", x1 + print " La moyenne de OMB (i.e. le biais) est de............:", x1.mean() + print " L'écart entre les observations et l'analyse est OMA :", x2 + print " La moyenne de OMA (i.e. le biais) est de............:", x2.mean() + # + D.calculate( vectorOMB = x1, vectorOMA = x2) + if not D.valueserie(1) : + print " Résultat : l'analyse NE RÉDUIT PAS le biais" + else : + print " Résultat : l'analyse RÉDUIT le biais" + print diff --git a/src/daComposant/daDiagnostics/ReduceVariance.py b/src/daComposant/daDiagnostics/ReduceVariance.py new file mode 100644 index 0000000..2272eac --- /dev/null +++ b/src/daComposant/daDiagnostics/ReduceVariance.py @@ -0,0 +1,116 @@ +#-*-coding:iso-8859-1-*- +# +# Copyright (C) 2008-2009 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +__doc__ = """ + Diagnostic sur la reduction de la variance lors de l'analyse +""" +__author__ = "Jean-Philippe ARGAUD - Septembre 2008" + +import sys ; sys.path.insert(0, "../daCore") + +import numpy +import Persistence +from BasicObjects import Diagnostic +from AssimilationStudy import AssimilationStudy + +# ============================================================================== +class ElementaryDiagnostic(Diagnostic,Persistence.OneScalar): + def __init__(self, name = "", unit = "", basetype = None, parameters = {}): + Diagnostic.__init__(self, name, parameters) + Persistence.OneScalar.__init__( self, name, unit, basetype = bool ) + + def _formula(self, V1, V2): + """ + Vérification de la reduction de variance sur les écarts entre OMB et OMA + lors de l'analyse + """ + varianceOMB = V1.var() + varianceOMA = V2.var() + # + if varianceOMA > varianceOMB: + reducevariance = False + else : + reducevariance = True + # + return reducevariance + + def calculate(self, vectorOMB = None, vectorOMA = None, step = None): + """ + Teste les arguments, active la formule de calcul et stocke le résultat + Arguments : + - vectorOMB : vecteur d'écart entre les observations et l'ébauche + - vectorOMA : vecteur d'écart entre les observations et l'analyse + """ + if ( (vectorOMB is None) or (vectorOMA is None) ): + raise ValueError("Two vectors must be given to test the reduction of the variance after analysis") + V1 = numpy.array(vectorOMB) + V2 = numpy.array(vectorOMA) + if V1.size < 1 or V2.size < 1: + raise ValueError("The given vectors must not be empty") + if V1.size != V2.size: + raise ValueError("The two given vectors must have the same size") + # + value = self._formula( V1, V2 ) + # + self.store( value = value, step = step ) + +#=============================================================================== +if __name__ == "__main__": + print '\n AUTODIAGNOSTIC \n' + # + # Instanciation de l'objet diagnostic + # ----------------------------------- + D = ElementaryDiagnostic("Mon ReduceVariance") + # + # Vecteur de type matrix + # ---------------------- + x1 = numpy.matrix(([3. , 4., 5. ])) + x2 = numpy.matrix(([1.5, 2., 2.5])) + print " L'écart entre les observations et l'ébauche est OMB :", x1 + print " La moyenne de OMB (i.e. le biais) est de............:", x1.mean() + print " La variance de OMB est de...........................:", x1.var() + print " L'écart entre les observations et l'analyse est OMA :", x2 + print " La moyenne de OMA (i.e. le biais) est de............:", x2.mean() + print " La variance de OMA est de...........................:", x2.var() + # + D.calculate( vectorOMB = x1, vectorOMA = x2) + if not D.valueserie(0) : + print " Résultat : l'analyse NE RÉDUIT PAS la variance" + else : + print " Résultat : l'analyse RÉDUIT la variance" + print + # + # Vecteur de type array + # --------------------- + x1 = numpy.array(range(11)) + x2 = numpy.matrix(range(-10,12,2)) + print " L'écart entre les observations et l'ébauche est OMB :", x1 + print " La moyenne de OMB (i.e. le biais) est de............:", x1.mean() + print " La variance de OMB est de...........................:", x1.var() + print " L'écart entre les observations et l'analyse est OMA :", x2 + print " La moyenne de OMA (i.e. le biais) est de............:", x2.mean() + print " La variance de OMA est de...........................:", x2.var() + # + D.calculate( vectorOMB = x1, vectorOMA = x2) + if not D.valueserie(1) : + print " Résultat : l'analyse NE RÉDUIT PAS la variance" + else : + print " Résultat : l'analyse RÉDUIT la variance" + print diff --git a/src/daComposant/daDiagnostics/StopReductionVariance.py b/src/daComposant/daDiagnostics/StopReductionVariance.py new file mode 100644 index 0000000..7ebe03a --- /dev/null +++ b/src/daComposant/daDiagnostics/StopReductionVariance.py @@ -0,0 +1,121 @@ +#-*-coding:iso-8859-1-*- +# +# Copyright (C) 2008-2009 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +__doc__ = """ + Diagnostic sur l'arrêt (ou le ralentissement) de la réduction de la variance + au fil des pas (ou itérations) de l'analyse. + Ce diagnostic s'applique typiquement au vecteur de différence entre la + variance de OMB et la variance de OMA au fil du temps ou des itérations: + V[i] = vecteur des VAR(OMB)[i] - VAR(OMA)[i] au temps ou itération i. +""" +__author__ = "Sophie Ricci - Septembre 2008" + +import sys ; sys.path.insert(0, "../daCore") + +import numpy +import Persistence +from BasicObjects import Diagnostic +from AssimilationStudy import AssimilationStudy + +# ============================================================================== +class ElementaryDiagnostic(Diagnostic,Persistence.OneScalar): + def __init__(self, name = "", unit = "", basetype = None, parameters = {}): + Diagnostic.__init__(self, name, parameters) + Persistence.OneScalar.__init__( self, name, unit, basetype = int ) + + def _formula(self, V, CutOffSlope, MultiSlope0): + """ + Recherche du pas de temps ou iteration pour laquelle la reduction + de la variance est + - inferieure a la valeur seuil CutOffSlope + (si une valeure est donnee a CutOffSlope) + - inferieure a MultiSlope0 * la pente a la premiere iteration + (si une valeure est donnee a MultiSlope0) + V[i] = vecteur des VAR(OMB)[i] - VAR(OMA)[i] au temps ou iteration i. + """ + N = V.size + pente = numpy.matrix(numpy.zeros((N,))).T + iterstopreduction = 0. + for i in range (1, N) : + pente[i] = V[i]- V[i-1] + if pente[i] > 0.0 : + raise ValueError("The analysis is INCREASING the variance a l iteration ", i) + if CutOffSlope is not None: + if numpy.abs(pente[i]) < CutOffSlope : + iterstopreduction = i + break + if MultiSlope0 is not None: + if numpy.abs(pente[i]) < MultiSlope0 * numpy.abs(pente[1]) : + iterstopreduction = i + break + # + return iterstopreduction + + def calculate(self, vector = None, CutOffSlope = None, MultiSlope0 = None, step = None) : + """ + Teste les arguments, active la formule de calcul et stocke le resultat + Arguments : + - vector : vecteur des VAR(OMB) - VAR(OMA) au fil des iterations + - CutOffSlope : valeur minimale de la pente + - MultiSlope0 : Facteur multiplicatif de la pente initiale pour comparaison + """ + if (vector is None) : + raise ValueError("One vector must be given to test the convergence of the variance after analysis") + V = numpy.array(vector) + if V.size < 1 : + raise ValueError("The given vector must not be empty") + if (MultiSlope0 is None) and (CutOffSlope is None) : + raise ValueError("You must set the value of ONE of the CutOffSlope of MultiSlope0 key word") + # + value = self._formula( V, CutOffSlope, MultiSlope0 ) + # + self.store( value = value, step = step ) + +#=============================================================================== +if __name__ == "__main__": + print "\n AUTODIAGNOSTIC \n" + + # Instanciation de l'objet diagnostic + # ------------------------------------------------ + D = ElementaryDiagnostic("Mon StopReductionVariance") + + # Vecteur de reduction VAR(OMB)-VAR(OMA) + # ------------------------------------------------ + x = numpy.array(([0.60898111, 0.30449056, 0.15224528, 0.07612264, 0.03806132, 0.01903066, 0.00951533, 0.00475766, 0.00237883, 0.00118942])) + print " Le vecteur choisi est :", x + print " Sur ce vecteur, la reduction a l iteration N = 7 est inferieure a 0.005" + print " Sur ce vecteur, la reduction a l iteration N = 8 est inferieure a 0.01 * la reduction a l iteration 1" + + # Comparaison a la valeur seuil de la reduction + # ------------------------------------------------ + D.calculate( vector = x, CutOffSlope = 0.005, MultiSlope0 = None) + if (D.valueserie(0) - 7.) < 1.e-15 : + print " Test : La comparaison a la valeur seuil de la reduction est juste" + else : + print " Test : La comparaison a la valeur seuil de la reduction est fausse" + + # Comparaison a alpha* la reduction a la premiere iteration + # ------------------------------------------------ + D.calculate( vector = x, CutOffSlope = None, MultiSlope0 = 0.01) + if (D.valueserie(1) - 8.) < 1.e-15 : + print " Test : La comparaison a la reduction a la premiere iteration est juste" + else : + print " Test : La comparaison a la reduction a la premiere iteration est fausse" + print diff --git a/src/daComposant/daDiagnostics/VarianceOrder.py b/src/daComposant/daDiagnostics/VarianceOrder.py new file mode 100644 index 0000000..202838e --- /dev/null +++ b/src/daComposant/daDiagnostics/VarianceOrder.py @@ -0,0 +1,129 @@ +#-*-coding:iso-8859-1-*- +# +# Copyright (C) 2008-2009 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +__doc__ = """ + Diagnostic sur les variances dans B et R par rapport à l'ébauche Xb et aux + observations Y. On teste si on a les conditions : + 1%*xb < sigma_b < 10%*xb + et + 1%*yo < sigma_o < 10%*yo + Le diagnostic renvoie True si les deux conditions sont simultanément + vérifiées, False dans les autres cas. +""" +__author__ = "Sophie RICCI, Jean-Philippe ARGAUD - Septembre 2008" + +import sys ; sys.path.insert(0, "../daCore") + +import numpy +import Persistence +from BasicObjects import Diagnostic +from scipy.linalg import eig +import logging + +# ============================================================================== +class ElementaryDiagnostic(Diagnostic,Persistence.OneScalar): + def __init__(self, name = "", unit = "", basetype = None, parameters = {}): + Diagnostic.__init__(self, name, parameters) + Persistence.OneScalar.__init__( self, name, unit, basetype = bool ) + + def _formula(self, xb, B, yo, R): + """ + Comparaison des variables et de leur variance relative + """ + valpB = eig(B, left = False, right = False) + valpR = eig(R, left = False, right = False) + logging.info(" Si l on souhaite 1%s*xb < sigma_b < 10%s*xb, les valeurs propres de B doivent etre comprises dans l intervalle [%.3e,%.3e]"%("%","%",1.e-4*xb.mean()*xb.mean(),1.e-2*xb.mean()*xb.mean())) + logging.info(" Si l on souhaite 1%s*yo < sigma_o < 10%s*yo, les valeurs propres de R doivent etre comprises dans l intervalle [%.3e,%.3e]"%("%","%",1.e-4*yo.mean()*yo.mean(),1.e-2*yo.mean()*yo.mean())) + # + limite_inf_valp = 1.e-4*xb.mean()*xb.mean() + limite_sup_valp = 1.e-2*xb.mean()*xb.mean() + variancexb = (valpB >= limite_inf_valp).all() and (valpB <= limite_sup_valp).all() + logging.info(" La condition empirique sur la variance de Xb est....: %s"%variancexb) + # + limite_inf_valp = 1.e-4*yo.mean()*yo.mean() + limite_sup_valp = 1.e-2*yo.mean()*yo.mean() + varianceyo = (valpR >= limite_inf_valp).all() and (valpR <= limite_sup_valp).all() + logging.info(" La condition empirique sur la variance de Y est.....: %s",varianceyo) + # + variance = variancexb and varianceyo + logging.info(" La condition empirique sur la variance globale est..: %s"%variance) + # + return variance + + def calculate(self, Xb = None, B = None, Y = None, R = None, step = None): + """ + Teste les arguments, active la formule de calcul et stocke le résultat + Arguments : + - Xb : valeur d'ébauche du paramêtre + - B : matrice de covariances d'erreur d'ébauche + - yo : vecteur d'observation + - R : matrice de covariances d'erreur d'observation + """ + if (Xb is None) or (B is None) or (Y is None) or (R is None): + raise ValueError("You must specify Xb, B, Y, R") + yo = numpy.array(Y) + BB = numpy.matrix(B) + xb = numpy.array(Xb) + RR = numpy.matrix(R) + if (RR.size < 1 ) or (BB.size < 1) : + raise ValueError("The background and the observation covariance matrices must not be empty") + if ( yo.size < 1 ) or ( xb.size < 1 ): + raise ValueError("The Xb background and the Y observation vectors must not be empty") + if xb.size*xb.size != BB.size: + raise ValueError("Xb background vector and B covariance matrix sizes are not consistent") + if yo.size*yo.size != RR.size: + raise ValueError("Y observation vector and R covariance matrix sizes are not consistent") + if yo.all() == 0. or xb.all() == 0. : + raise ValueError("The diagnostic can not be applied to zero vectors") + # + value = self._formula( xb, BB, yo, RR) + # + self.store( value = value, step = step ) + +#=============================================================================== +if __name__ == "__main__": + print '\n AUTODIAGNOSTIC \n' + # + # Instanciation de l'objet diagnostic + # ----------------------------------- + D = ElementaryDiagnostic("Mon OrdreVariance") + # + # Vecteur de type matrix + # ---------------------- + xb = numpy.array([11000.]) + yo = numpy.array([1.e12 , 2.e12, 3.e12 ]) + B = 1.e06 * numpy.matrix(numpy.identity(1)) + R = 1.e22 * numpy.matrix(numpy.identity(3)) + # + D.calculate( Xb = xb, B = B, Y = yo, R = R) + print " L'ébauche est.......................................:",xb + print " Les observations sont...............................:",yo + print " La valeur moyenne des observations est..............: %.2e"%yo.mean() + print " La valeur moyenne de l'ebauche est..................: %.2e"%xb.mean() + print " La variance d'ébauche specifiée est.................: %.2e"%1.e6 + print " La variance d'observation spécifiée est.............: %.2e"%1.e22 + # + if D.valueserie(0) : + print " Les variances specifiées sont de l'ordre de 1% a 10% de l'ébauche et des observations" + else : + print " Les variances specifiées ne sont pas de l'ordre de 1% a 10% de l'ébauche et des observations" + print + + diff --git a/src/daComposant/daDiagnostics/__init__.py b/src/daComposant/daDiagnostics/__init__.py new file mode 100644 index 0000000..6bcb582 --- /dev/null +++ b/src/daComposant/daDiagnostics/__init__.py @@ -0,0 +1,19 @@ +# +# Copyright (C) 2008-2009 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# diff --git a/src/daComposant/daExternals/ASTER/Building_AD_from_Aster.xml b/src/daComposant/daExternals/ASTER/Building_AD_from_Aster.xml new file mode 100644 index 0000000..51be879 --- /dev/null +++ b/src/daComposant/daExternals/ASTER/Building_AD_from_Aster.xml @@ -0,0 +1,275 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Building_Bparametres + (lp1 +. + + + Building_Xbparametres + (lp1 +. + + + Building_Yocalcul + (lp1 +. + + + Building_Yoexperiences + (lp1 +. + + + Sorties du calcul ADxa + + + + + Sorties du calcul ADA + + + + + Sorties du calcul ADInnovation + + + + + Sorties du calcul ADxb + + + + + Sorties du calcul ADYo + + + + + Sorties du calcul ADB + + + + + Sorties du calcul ADR + + + + + Sorties du calcul ADH + + + + + Building_Rexperiences + (lp1 +. + + + Entrees du calcul ADXb + + + + + Entrees du calcul ADYo + + + + + Entrees du calcul ADB + + + + + Entrees du calcul ADR + + + + + Entrees du calcul ADH + + + + + + + + + + + diff --git a/src/daComposant/daExternals/ASTER/Building_H_linear.xml b/src/daComposant/daExternals/ASTER/Building_H_linear.xml new file mode 100644 index 0000000..9bc3e25 --- /dev/null +++ b/src/daComposant/daExternals/ASTER/Building_H_linear.xml @@ -0,0 +1,335 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Perturbated_point_X ASTER + + Perturbated_point_X X + ASTER X + + + Perturbated_point_X iter + ASTER iter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Finite_differences_derivation Gradient + Input Finite_differences_derivation + Input Gradient + Temporary_Parameters Finite_differences_derivation + Temporary_Parameters Gradient + + Finite_differences_derivation SmplPrt + Finite_differences_derivation.Elementary_calculation.Perturbated_point_X iter + + + Input nbBranches + Finite_differences_derivation nbBranches + + + Input itervect + Finite_differences_derivation SmplsCollection + + + Input seq_X + Finite_differences_derivation.Elementary_calculation.Perturbated_point_X seq_X + + + Input dX + Gradient dX + + + Temporary_Parameters ASTER_ROOT + Finite_differences_derivation.Elementary_calculation.ASTER ASTER_ROOT + + + Temporary_Parameters rcdir + Finite_differences_derivation.Elementary_calculation.ASTER rcdir + + + Temporary_Parameters debug + Finite_differences_derivation.Elementary_calculation.ASTER debug + + + Temporary_Parameters DISPLAY + Finite_differences_derivation.Elementary_calculation.ASTER DISPLAY + + + Temporary_Parameters SOURCES_ROOT + Finite_differences_derivation.Elementary_calculation.ASTER SOURCES_ROOT + + + Temporary_Parameters SOURCES_ROOT + Gradient SOURCES_ROOT + + + Temporary_Parameters export + Finite_differences_derivation.Elementary_calculation.ASTER export + + + Temporary_Parameters parametres + Finite_differences_derivation.Elementary_calculation.ASTER parametres + + + Temporary_Parameters calcul + Finite_differences_derivation.Elementary_calculation.ASTER calcul + + + Temporary_Parameters experience + Finite_differences_derivation.Elementary_calculation.ASTER experience + + + Temporary_Parameters fileparameters + Finite_differences_derivation.Elementary_calculation.ASTER fileparameters + + + Finite_differences_derivation.Elementary_calculation.ASTER FX + Gradient seq_FX + + + Finite_differences_derivation.Elementary_calculation.ASTER FY + Gradient seq_FY + + + Finite_differences_derivation.Elementary_calculation.ASTER DIMS + Gradient seq_DIMS + + + Finite_differences_derivation.Elementary_calculation.ASTER DIAG + Gradient lst_DIAG + + + Finite_differences_derivation.Elementary_calculation.ASTER iter + Gradient lst_iter + + + + H_linearization.Finite_differences_derivation.Elementary_calculation.ASTERX + +80000 +1000 +30 + + + + H_linearization.Temporary_ParametersASTER_ROOT + '' + + + H_linearization.Temporary_Parametersrcdir + '' + + + H_linearization.Temporary_Parametersdebug + 0 + + + H_linearization.Temporary_ParametersDISPLAY + :0.0 + + + H_linearization.Temporary_ParametersSOURCES_ROOT + . + + + H_linearization.Temporary_Parametersexport + '' + + + H_linearization.Temporary_Parametersparametres + + + + H_linearization.Temporary_Parameterscalcul + + + + H_linearization.Temporary_Parametersexperience + + + + H_linearization.Temporary_Parametersfileparameters + [] + + + H_linearization.InputX + +80000 +1000 +30 + + + + H_linearization.InputdX + +0.001 +0.001 +0.0001 + + + + + + + + + + + + diff --git a/src/daComposant/daExternals/YACS/Algorithmes_AD.xml b/src/daComposant/daExternals/YACS/Algorithmes_AD.xml new file mode 100644 index 0000000..fd2ac9e --- /dev/null +++ b/src/daComposant/daExternals/YACS/Algorithmes_AD.xml @@ -0,0 +1,252 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BLUE par matricesYo + + + + + BLUE par matricesB + + + + + BLUE par matricesR + + + + + BLUE par matricesH + + + + + BLUE par matricesXb + + + + + 3D-VAR par matricesYo + + + + + 3D-VAR par matricesB + + + + + 3D-VAR par matricesR + + + + + 3D-VAR par matricesH + + + + + 3D-VAR par matricesXb + + + + + 3D-VAR par fonctionsYo + + + + + 3D-VAR par fonctionsB + + + + + 3D-VAR par fonctionsR + + + + + 3D-VAR par fonctionsXb + + + + + 3D-VAR par fonctionsBounds + (lp1 +. + + + + + + diff --git a/src/daComposant/daExternals/__init__.py b/src/daComposant/daExternals/__init__.py new file mode 100644 index 0000000..6bcb582 --- /dev/null +++ b/src/daComposant/daExternals/__init__.py @@ -0,0 +1,19 @@ +# +# Copyright (C) 2008-2009 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# diff --git a/src/daComposant/daMatrices/__init__.py b/src/daComposant/daMatrices/__init__.py new file mode 100644 index 0000000..6bcb582 --- /dev/null +++ b/src/daComposant/daMatrices/__init__.py @@ -0,0 +1,19 @@ +# +# Copyright (C) 2008-2009 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# diff --git a/src/daComposant/daNumerics/ComputeFisher.py b/src/daComposant/daNumerics/ComputeFisher.py new file mode 100644 index 0000000..d6c46f7 --- /dev/null +++ b/src/daComposant/daNumerics/ComputeFisher.py @@ -0,0 +1,116 @@ +#-*-coding:iso-8859-1-*- +# +# Copyright (C) 2008-2009 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +__doc__ = """ + Outil numérique de calcul de la variable de Fisher pour comparer les + variances de 2 échantillons + + Ce calcul nécessite : + - en input : + - les deux vecteurs (comme liste, array ou matrix) d'échantillons + dont on veut comparer la variance, + - la tolérance + - en output : + - la p-value, + - la valeur de la variable aléatoire, + - la réponse au test ainsi que + - le message qui interprete la reponse du test. +""" +__author__ = "Sophie RICCI - Juillet 2008" + +import numpy +from scipy.stats import betai + +# ============================================================================== +def ComputeFisher(vector1 = None, vector2 = None, tolerance = 0.05 ): + """ + Outil numérique de calcul de la variable de Fisher pour comparer les + variances de 2 échantillons + + Ce calcul nécessite : + - en input : les deux vecteurs (comme liste, array ou matrix) + d'échantillons dont on veut comparer la variance, la + tolérance + - en output : la p-value, la valeur de la variable aléatoire, + la réponse au test ainsi que le message qui interprete + la reponse du test. + """ + if (vector1 is None) or (vector2 is None) : + raise ValueError("Two vectors must be given to calculate the Fisher value value") + V1 = numpy.array(vector1) + V2 = numpy.array(vector2) + if (V1.size < 1) or (V2.size < 1): + raise ValueError("The given vectors must not be empty") + # + # Calcul des variances des echantillons + # ------------------------------------- + # où var est calculee comme : var = somme (xi -xmean)**2 /(n-1) + n1 = V1.size + n2 = V2.size + var1 = V1.std() * V1.std() + var2 = V2.std() * V2.std() + if (var1 > var2): + f = var1/var2 + df1 = n1-1 + df2 = n2-1 + else: + f= var2/var1 + df1 = n2-1 + df2 = n1-1 + prob1= betai(0.5*df2,0.5*df1,float(df2)/float(df2+df1*f)) + prob2= (1. - betai(0.5*df1, 0.5*df2, float(df1)/float(df1+df2/f))) + prob = prob1 + prob2 + # + # Calcul de la p-value + # -------------------- + areafisher = 100 * prob + # + # Test + # ---- + message = "Il y a %.2f%s de chance de se tromper en refusant l'hypothèse d'égalité des variances des 2 échantillons (si <%.2f%s, on refuse effectivement l'égalité)"%(areafisher,"%",100.*tolerance,"%") + if (areafisher < (100.*tolerance)) : + answerTestFisher = False + else: + answerTestFisher = True + # print "La reponse au test est", answerTestFisher + + return areafisher, f, answerTestFisher, message + +# ============================================================================== +if __name__ == "__main__": + print "\nAUTOTEST\n" + # + # Echantillons + # ------------ + x1 = [-1., 0., 4., 2., -1., 3.] + x2 = [-1., 0., 4., 2., -1., 3.] + # + # Appel du calcul + # --------------- + [aire, f, reponse, message] = ComputeFisher( + vector1 = x1, + vector2 = x2, + tolerance = 0.05 ) + # + print " aire.....:", aire + print " f........:", f + print " reponse..:", reponse + print " message..:", message + print diff --git a/src/daComposant/daNumerics/ComputeKhi2.py b/src/daComposant/daNumerics/ComputeKhi2.py new file mode 100644 index 0000000..31e26d0 --- /dev/null +++ b/src/daComposant/daNumerics/ComputeKhi2.py @@ -0,0 +1,420 @@ +#-*-coding:iso-8859-1-*- +# +# Copyright (C) 2008-2009 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +__doc__ = """ + Outil numerique de calcul de la variable Khi2 + + On peut realiser deux types de test du Khi2 : + - test d'adequation : comparer la distribution d'un echantillon a une + distribution theorique, + - test d'homogeneite : comparer les distributions de 2 vecteurs. + + Pour le test d'adequation, on travaille sur une gaussienne + dont la moyenne et l'ecart type sont calcules sur + l'echantillon, soit donnes. + + Ce fichier contient une classe "StatspourTests" de methodes qui realisent + differentes etapes utiles aux calculs des tests du Khi2. + + Ce fichier contient de plus 3 methodes : ComputeKhi2_testGauss, + ComputeKhi2_Gauss et ComputeKhi2_Homogen. + - ComputeKhi2_testGauss : calcul la distance du Khi2 entre un vecteur + aleatoire issu d un gaussienne et une distribution theorique gaussienne + dont on specifie la moyenne et l ecart type + - ComputeKhi2_Gauss : calcul la distance du Khi2 entre un vecteur donne et + une distribution theorique gaussienne dont la moyenne et l ecart type sont + calcules sur l echantillon + - ComputeKhi2_Homogen : calcul la distance du Khi2 entre deux vecteurs donnes + + Ces methodes necessitent et fournissent : + - en input : + - le ou les vecteurs dont on etudie la distribution, + - la distribution theorique et eventuellement la moyenne et ecart type, + - la largeur des classes, + - un booleen traduisant la suppression des classes vides + - en output : + - le vecteur des classes, + - les pdf theorique et donnee, + - la valeur du Khi2, + - la p-value qui represent l'aire de la queue de la distribution du + Khi2 et + - le message qui interprete le test. +""" +__author__ = "Sophie RICCI - Mars 2010" + +import numpy +from numpy import random +from scipy import arange, asarray, stats +from scipy.stats import histogram2, chisquare, chisqprob, norm +import logging + +# ============================================================================== +class StatspourTests : + """ + Classe de methodes pour la preparation du test de Khi2 + """ + def __init__(self, cdftheo=None, meantheo = None, stdtheo = None, pdftest=None,obs=None,use_mean_std_exp=True, dxmin=0.01, obsHomogen = None, nbclasses = None) : + + + if (pdftest is None and obs is None) : + raise ValueError('Donner soit une pdf de test soit un vecteur obs') + if not obs is None : + if pdftest is None : + self.__obs=asarray(obs) + if not pdftest is None : + if obs is None : + if len(pdftest) == 3: + niter=eval(pdftest[2]) + obs=[eval(" ".join(pdftest[:2])) for z in range(niter)] + self.__obs=asarray(obs) + else : + self.__obs=asarray(eval(" ".join(pdftest[:2]))) + if not (obsHomogen is None) : + self.__obsHomogen = asarray(obsHomogen) + self.__testHomogen = True + else : + self.__testHomogen = False + + + self.__mean_exp = self.__obs.mean() + self.__std_exp = self.__obs.std() + + if cdftheo is None : raiseValueError(" ... Definir le parametre cdftheo ...") + if use_mean_std_exp : + self.__cdf=cdftheo( self.__mean_exp, self.__std_exp).cdf + else : + self.__cdf=cdftheo( meantheo, stdtheo).cdf + + self.__min=min(self.__obs) + self.__max=max(self.__obs) + self.__N=len(self.__obs) + self.__use_mean_std_exp=use_mean_std_exp + self.__dxmin=dxmin + self.__nbclasses = nbclasses + if not (dxmin is None) and not (nbclasses is None) : + raise ValueError("... Specifier soit le nombre de classes, soit la largeur des classes") + if (dxmin is None) and (nbclasses is None) : + raise ValueError("... Specifier soit le nombre de classes, soit la largeur des classes") + if not (nbclasses is None) and (dxmin is None) : + self.__dxmin = (self.__max - self.__min ) / float(self.__nbclasses) + return None + + def MakeClasses(self) : + """ + Classification en classes + """ + self.__subdiv=arange(self.__min,self.__max+self.__dxmin,self.__dxmin) + self.__modalites=len(self.__subdiv) + return None + + def ComputeObs(self): + """ + Calcul de la probabilite observee de chaque classe + """ + self.__kobs=histogram2(self.__obs,self.__subdiv)[1:] + return self.__kobs + + def ComputeObsHomogen(self): + """ + Calcul de la probabilite observee pour le test homogeneite de chaque classe + """ + self.__kobsHomogen=histogram2(self.__obsHomogen,self.__subdiv)[1:] + return self.__kobsHomogen + + def ComputeTheo(self): + """ + Calcul de la probabilite theorique de chaque classe + """ + self.__ktheo=[self.__cdf(self.__subdiv[i+1])-self.__cdf(self.__subdiv[i]) for i in range(self.__modalites-1)] + self.__ktheo=asarray(self.__ktheo) + self.__ktheo=(sum(self.__kobs)/sum(self.__ktheo))*self.__ktheo + + def Computepdfs(self) : + + self.__subdiv=self.__subdiv[1:] + self.__pdfobs=[self.__kobs[i+1]/(self.__subdiv[i+1]-self.__subdiv[i]) for i in range(self.__modalites-2)] + + if self.__testHomogen : + self.__pdftheo=[self.__kobsHomogen[i+1]/(self.__subdiv[i+1]-self.__subdiv[i]) for i in range(self.__modalites-2)] + else : + self.__pdftheo=[self.__ktheo[i+1]/(self.__subdiv[i+1]-self.__subdiv[i]) for i in range(self.__modalites-2)] + + return self.__subdiv, self.__pdftheo, self.__pdfobs + + def Computeddl(self): + """ + Calcul du nombre de degres de liberte + """ + self.__ddl = self.__modalites - 1. + if self.__use_mean_std_exp : + self.__ddl = self.__ddl - 2. + if (self.__ddl < 1.): + raise ValueError("The ddl is 0, you must increase the number of classes nbclasse ") + logging.debug("Nombre de degres de liberte=%s"%self.__ddl) + + def ComputeValue(self) : + """ + Calcul de la variable Q qui suit une loi Khi-2 + """ + if self.__testHomogen : + kobs,ktheo=self.__kobs.tolist(),self.__kobsHomogen.tolist() + else : + kobs,ktheo=self.__kobs.tolist(),self.__ktheo.tolist() + + # on supprime les classes theoriques qui ont moins d'un element (sinon la distance khi2 tendrait vers l'infini) + ko,kt=[],[] + self.__count0 = 0. + for k,val in enumerate(ktheo): + if val > 1.0: + kt.append(val) + ko.append(kobs[k]) + else : + self.__count0 = self.__count0 +1. + logging.debug("WARNING : nombre de classes vides supprimees (effectif theorique inferieur a 1.) pour le calcul de la valeur du Khi2 = %s"%self.__count0) + ef1,ef2=asarray(ko),asarray(kt) + count = 0. + for el in ef1.tolist() : + if el < 5. : + count = count +1. + for el in ef2.tolist() : + if el < 5. : + count = count +1. + pourcent_nbclasse_effecinf = count /(2.*len(ef1.tolist())) *100. + if (pourcent_nbclasse_effecinf > 20.) : + logging.debug("WARNING : nombre de classes dont l effectif est inferieur a 5 elements %s"%pourcent_nbclasse_effecinf) + k,p = chisquare(ef1, ef2) + k2, p2 = [k],[p] + for shift in range(1,6): + k,p=chisquare(ef1[shift:],ef2[:-shift]) + k2.append(k) + p2.append(p) + k,p=chisquare(ef1[:-shift],ef2[shift:]) + k2.append(k) + p2.append(p) + logging.debug("Liste des valeurs du Khi2 = %s"%k2) + self.__khi2=min(k2) + self.__Q=self.__khi2 + + logging.debug("Valeur du Khi2=%s"%self.__Q) + return self.__Q + + def ComputeArea(self): + """ + Calcul de la p-value + """ + self.__areakhi2 = 100 * chisqprob(self.__Q, self.__ddl) + return self.__areakhi2 + + def WriteMessage(self): + """ + Interpretation du test + """ + message = "Il y a %.2f%s de chance de se tromper en refusant l'adequation"%(self.__areakhi2,"%") + return message + + def WriteMessageHomogen(self): + """ + Interpretation du test + """ + message = "Il y a %.2f%s de chance de se tromper en refusant l'homogeneite"%(self.__areakhi2,"%") + return message + +# ============================================================================== +def ComputeKhi2_testGauss( + meantheo = 0., + stdtheo = 1., + nech = 10, + dx = 0.1, + nbclasses = None, + SuppressEmptyClasses = True, + ): + """ + Test du Khi2 d adequation entre tirage aleatoire dans gaussienne et une gaussienne theo + """ + essai = StatspourTests( cdftheo=norm, meantheo = meantheo, stdtheo = stdtheo, pdftest = ["random.normal","(%.3f,%.2f,%d)"%(meantheo,stdtheo,nech)], obs = None, use_mean_std_exp=False,dxmin=dx, obsHomogen = None, nbclasses = nbclasses) + essai.MakeClasses() + essai.ComputeObs() + essai.ComputeTheo() + classes,eftheo, efobs = essai.Computepdfs() + essai.Computeddl() + valeurKhi2= essai.ComputeValue() + areaKhi2 = essai.ComputeArea() + message = essai.WriteMessage() + logging.debug("message %s"%message) + return classes, eftheo, efobs, valeurKhi2, areaKhi2, message + +def ComputeKhi2_Gauss( + vectorV = None, + dx = 0.1, + SuppressEmptyClasses = True, + nbclasses = None + ): + """ + Test du Khi2 d adequation entre un vecteur donne et une gaussienne theo de mean et std celles du vecteur + """ + essai = StatspourTests( cdftheo=norm, pdftest = None, obs = vectorV, use_mean_std_exp=True,dxmin=dx, obsHomogen = None, nbclasses = nbclasses) + essai.MakeClasses() + essai.ComputeObs() + essai.ComputeTheo() + classes,eftheo, efobs = essai.Computepdfs() + essai.Computeddl() + valeurKhi2= essai.ComputeValue() + areaKhi2 = essai.ComputeArea() + message = essai.WriteMessage() + logging.debug("message %s"%message) + return classes, eftheo, efobs, valeurKhi2, areaKhi2, message + +def ComputeKhi2_Homogen( + vectorV1 = None, + vectorV2 = None, + dx = 0.1, + SuppressEmptyClasses = True, + nbclasses = None + ): + """ + Test du Khi2 d homogeniete entre 2 vecteurs + """ + essai = StatspourTests( cdftheo=norm, pdftest = None, obs = vectorV1, use_mean_std_exp=True,dxmin=dx, obsHomogen = vectorV2, nbclasses = nbclasses) + essai.MakeClasses() + essai.ComputeObs() + essai.ComputeObsHomogen() + classes,eftheo, efobs = essai.Computepdfs() + essai.Computeddl() + valeurKhi2= essai.ComputeValue() + areaKhi2 = essai.ComputeArea() + message = essai.WriteMessageHomogen() + logging.debug("message %s"%message) + return classes, eftheo, efobs, valeurKhi2, areaKhi2, message + +# ============================================================================== +if __name__ == "__main__": + print '\n AUTODIAGNOSTIC \n' + # + numpy.random.seed(100) + + # Test de verification d adequation entre une gaussienne et un tirage gaussien + print '' + print 'Test de verification d adequation entre une gaussienne centree normale et un tirage gaussien' + classes, eftheo, efobs, valeurKhi2, areaKhi2, message = ComputeKhi2_testGauss(meantheo = 0., stdtheo = 1., nech = 1000., dx = 0.1, SuppressEmptyClasses = True, nbclasses = None) + print ' valeurKhi2=',valeurKhi2 + print ' areaKhi2=',areaKhi2 + print ' ',message + + if (numpy.abs(areaKhi2 - 99.91)< 1.e-2) : + print "The computation of the khisquare value is OK" + else : + raise ValueError("The computation of the khisquare value is WRONG") + + numpy.random.seed(2490) + + # Test de verification d adequation entre une gaussienne et un vecteur donne + print '' + print 'Test de verification d adequation entre une gaussienne et un vecteur donne' + V = random.normal(50.,1.5,1000) + classes, eftheo, efobs, valeurKhi2, areaKhi2, message = ComputeKhi2_Gauss(dx = 0.1, vectorV = V, SuppressEmptyClasses = True, nbclasses = None) + print ' valeurKhi2=',valeurKhi2 + print ' areaKhi2=',areaKhi2 + print ' ',message + + if (numpy.abs(areaKhi2 - 99.60)< 1.e-2) : + print "The computation of the khisquare value is OK" + else : + raise ValueError("The computation of the khisquare value is WRONG") + + # Test de d homogeneite entre 2 vecteurs donnes + print '' + print 'Test d homogeneite entre 2 vecteurs donnes' + V1 = random.normal(50.,1.5,10000) + numpy.random.seed(2490) + V2 = random.normal(50.,1.5,10000) + classes, eftheo, efobs, valeurKhi2, areaKhi2, message = ComputeKhi2_Homogen(dx = 0.5, vectorV1 = V1, vectorV2 = V2, SuppressEmptyClasses = True, nbclasses = None) + print ' valeurKhi2=',valeurKhi2 + print ' areaKhi2=',areaKhi2 + print ' ',message + + if (numpy.abs(areaKhi2 - 99.98)< 1.e-2) : + print "The computation of the khisquare value is OK" + else : + raise ValueError("The computation of the khisquare value is WRONG") + + # Test de verification d adequation entre une gaussienne et un tirage gaussien en faisant varier le nombre de classes, echantillon de taille 10000 + print '' + print 'Test de verification d adequation entre une gaussienne et un vecteur aleatoire gaussien de taille 10000' +# file = 'ComputeKhi2_adequationGauss_fctnbclasses_nech10000.gnu' +# fid = open(file, "w") +# lines = '%s\n' % ('# dx , nbclasses, valeurKhi2, ProbKhi2' ) + numpy.random.seed(4000) + V = random.normal(0., 1.,10000) + aire = [] + for dx in arange(0.01, 1., 0.001) : + classes, eftheo, efobs, valeurKhi2, areaKhi2, message = ComputeKhi2_Gauss(dx = dx, vectorV = V, SuppressEmptyClasses = True, nbclasses = None) +# lines += '%f %f %f %f\n' % (dx, numpy.size(classes), valeurKhi2, areaKhi2) + aire.append(areaKhi2) + meanaire = numpy.asarray(aire) +# fid.writelines(lines) + + print " En moyenne, il y a ", meanaire.mean(),"% de chance de se tromper en refusant l adequation a une loi gaussienne pour un echantillon de taille 10000" + print + if (numpy.abs( meanaire.mean() - 71.79)< 1.e-2) : + print "The computation of the khisquare value is OK" + else : + raise ValueError("The computation of the khisquare value is WRONG") + + # Test de verification d adequation entre une gaussienne et un tirage gaussien en faisant varier le nombre de classes, echantillon de taille 1000 + print '' + print 'Test de verification d adequation entre une gaussienne et un vecteur aleatoire gaussien de taille 1000' +# file = 'ComputeKhi2_adequationGauss_fctnbclasses_nech1000.gnu' +# fid = open(file, "w") +# lines = '%s\n' % ('# dx , nbclasses, valeurKhi2, ProbKhi2' ) + numpy.random.seed(4000) + V = random.normal(0., 1.,1000) + aire = [] + for dx in arange(0.05, 1., 0.001) : + classes, eftheo, efobs, valeurKhi2, areaKhi2, message = ComputeKhi2_Gauss(dx = dx, vectorV = V, SuppressEmptyClasses = True, nbclasses = None) +# lines += '%f %f %f %f\n' % (dx, numpy.size(classes), valeurKhi2, areaKhi2) + aire.append(areaKhi2) + meanaire = numpy.asarray(aire) +# fid.writelines(lines) + + print " En moyenne, il y a ", meanaire.mean(),"% de chance de se tromper en refusant l adequation a une loi gaussienne pour un echantillon de taille 1000" + print + if (numpy.abs( meanaire.mean() - 90.60)< 1.e-2) : + print "The computation of the khisquare value is OK" + else : + raise ValueError("The computation of the khisquare value is WRONG") + + # Test de verification d adequation entre une gaussienne et un tirage gaussien en faisant varier le nombre de classes, echantillon de taille 100 + print '' + print 'Test de verification d adequation entre une gaussienne et un vecteur aleatoire gaussien de taille 100' +# file = 'ComputeKhi2_adequationGauss_fctnbclasses_nech100.gnu' +# fid = open(file, "w") +# lines = '%s\n' % ('# dx , nbclasses, valeurKhi2, ProbKhi2' ) + numpy.random.seed(4000) + V = random.normal(0., 1.,100) + aire = [] + for dx in arange(0.1, 1., 0.01) : + classes, eftheo, efobs, valeurKhi2, areaKhi2, message = ComputeKhi2_Gauss(dx = dx, vectorV = V, SuppressEmptyClasses = True, nbclasses = None) +# lines += '%f %f %f %f\n' % (dx, numpy.size(classes), valeurKhi2, areaKhi2) + aire.append(areaKhi2) + meanaire = numpy.asarray(aire) +# fid.writelines(lines) + + print " En moyenne, il y a ", meanaire.mean(),"% de chance de se tromper en refusant l adequation a une loi gaussienne pour un echantillon de taille 100" + print diff --git a/src/daComposant/daNumerics/ComputeStudent.py b/src/daComposant/daNumerics/ComputeStudent.py new file mode 100644 index 0000000..3736490 --- /dev/null +++ b/src/daComposant/daNumerics/ComputeStudent.py @@ -0,0 +1,260 @@ +#-*-coding:iso-8859-1-*- +# +# Copyright (C) 2008-2009 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +__doc__ = """ + Outil numérique de calcul des variables de Student pour 2 vecteurs + dépendants ou indépendants, avec variances supposées égales ou différentes +""" +__author__ = "Sophie RICCI, Jean-Philippe ARGAUD - Octobre 2008" + +import sys ; sys.path.insert(0, "../daCore") + +import numpy +from scipy.stats import ttest_rel, ttest_ind, betai +import logging + +# ============================================================================== +def DependantVectors(vector1 = None, vector2 = None, tolerance = 0.05 ): + """ + Outil numérique de calcul de la variable de Student pour 2 vecteurs + dépendants + Ce calcul nécessite : + - en input : + - les deux vecteurs (comme liste, array ou matrix) + d'échantillons dont on veut comparer la variance, + - la tolérance + - en output : + - la p-value, + - la valeur de la variable aléatoire, + - la reponse au test pour une tolerance ainsi que + - le message qui interprete la reponse du test. + """ + if (vector1 is None) or (vector2 is None) : + raise ValueError("Two vectors must be given to calculate the Student value") + V1 = numpy.array(vector1) + V2 = numpy.array(vector2) + if (V1.size < 1) or (V2.size < 1): + raise ValueError("The given vectors must not be empty") + if V1.size != V2.size: + raise ValueError("The two given vectors must have the same size, or the vector types are incompatible") + # + # Calcul de la p-value du Test de Student + # -------------------------------------------------------------------- + [t, prob] = ttest_rel(V1, V2) + areastudent = 100. * prob + # + logging.debug("DEPENDANTVECTORS t = %.3f, areastudent = %.3f"%(t, areastudent)) + # + # Tests + # -------------------------------------------------------------------- + message = "DEPENDANTVECTORS Il y a %.2f %s de chance de se tromper en refusant l'hypothèse d'égalité des moyennes des 2 échantillons dépendants (si <%.2f %s on refuse effectivement l'égalité)"%(areastudent, "%", 100.*tolerance,"%") + logging.debug(message) + # + if (areastudent < (100.*tolerance)) : + answerTestStudent = False + else: + answerTestStudent = True + # + return areastudent, t, answerTestStudent, message + +# ============================================================================== +def IndependantVectorsDifferentVariance(vector1 = None, vector2 = None, tolerance = 0.05 ): + """ + Outil numerique de calcul de la variable de Student pour 2 vecteurs independants supposes de variances vraies differentes + En input : la tolerance + En output : la p-value, la valeur de la variable aleatoire, la reponse au test pour une tolerance ainsi que le message qui interprete la reponse du test. + """ + if (vector1 is None) or (vector2 is None) : + raise ValueError("Two vectors must be given to calculate the Student value") + V1 = numpy.array(vector1) + V2 = numpy.array(vector2) + if (V1.size < 1) or (V2.size < 1): + raise ValueError("The given vectors must not be empty") + # + # Calcul de la p-value du Test de Student + # -------------------------------------------------------------------- + # t = (m1 - m2)/ sqrt[ (var1/n1 + var2/n2) ] + # ou var est calcule comme var = somme (xi -xmena)**2 /(n-1) + n1 = V1.size + n2 = V2.size + mean1 = V1.mean() + mean2 = V2.mean() + var1 = numpy.sqrt(n1)/numpy.sqrt(n1-1) * V1.std() * numpy.sqrt(n1)/numpy.sqrt(n1-1) * V1.std() + var2 = numpy.sqrt(n2)/numpy.sqrt(n2-1) * V2.std() * numpy.sqrt(n2)/numpy.sqrt(n2-1) * V2.std() + t = (mean1 - mean2)/ numpy.sqrt( var1/n1 + var2/n2 ) + df = ( (var1/n1 + var2/n2) * (var1/n1 + var2/n2) ) / ( (var1/n1)*(var1/n1)/(n1-1) + (var2/n2)*(var2/n2)/(n2-1) ) + zerodivproblem = var1/n1 + var2/n2 == 0 + t = numpy.where(zerodivproblem, 1.0, t) # replace NaN t-values with 1.0 + prob = betai(0.5*df,0.5,float(df)/(df+t*t)) + areastudent = 100. * prob + # + logging.debug("IndependantVectorsDifferentVariance t = %.3f, areastudent = %.3f"%(t, areastudent)) + # + # Tests + # -------------------------------------------------------------------- + message = "IndependantVectorsDifferentVariance Il y a %.2f %s de chance de se tromper en refusant l'hypothèse d'égalité des moyennes des 2 échantillons indépendants supposés de variances différentes (si <%.2f %s on refuse effectivement l'égalité)"%(areastudent, "%", 100.* tolerance,"%") + logging.debug(message) + if (areastudent < (100.*tolerance)) : + answerTestStudent = False + else: + answerTestStudent = True + # + return areastudent, t, answerTestStudent, message + +# ============================================================================== +def IndependantVectorsEqualVariance(vector1 = None, vector2 = None, tolerance = 0.05 ): + """ + Outil numerique de calcul de la variable de Student pour 2 vecteurs independants supposes de meme variance vraie + En input : la tolerance + En output : la p-value, la valeur de la variable aleatoire, la reponse au test pour une tolerance ainsi que le message qui interprete la reponse du test. + """ + if (vector1 is None) or (vector2 is None) : + raise ValueError("Two vectors must be given to calculate the Student value") + V1 = numpy.array(vector1) + V2 = numpy.array(vector2) + if (V1.size < 1) or (V2.size < 1): + raise ValueError("The given vectors must not be empty") + # + # Calcul de la p-value du Test de Student + # -------------------------------------------------------------------- + # t = sqrt(n1+n2-2) * (m1 - m2)/ sqrt[ (1/n1 +1/n2) * ( (n1-1)var1 + (n2-1)var2 )] + # ou var est calcule comme var = somme (xi -xmena)**2 /(n-1) + [t, prob] = ttest_ind(V1, V2) + areastudent = 100. * prob + # + logging.debug("IndependantVectorsEqualVariance t = %.3f, areastudent = %.3f"%(t, areastudent)) + # Tests + # -------------------------------------------------------------------- + message = "IndependantVectorsEqualVariance Il y a %.2f %s de chance de se tromper en refusant l'hypothèse d'égalité des moyennes des 2 échantillons indépendants supposés de même variance (si <%.2f %s on refuse effectivement l'égalité)"%(areastudent, "%", 100.* tolerance,"%") + logging.debug(message) + if (areastudent < (100.*tolerance)) : + answerTestStudent = False + else: + answerTestStudent = True + + return areastudent, t, answerTestStudent, message + +# ============================================================================== +if __name__ == "__main__": + print '\n AUTODIAGNOSTIC \n' + # logging.getLogger().setLevel(logging.DEBUG) + + print + print " Test de Student pour des vecteurs dépendants" + print " --------------------------------------------" + # Tirage de l'echantillon + V1 = numpy.matrix(([-1., 0., 4.])).T + V2 = numpy.matrix(([-2., 0., 8.])).T + V1 = V1.A1 + V2 = V2.A1 + # + # Appel de l outil DependantVectors et initialisation des inputs + [aire, Q, reponse, message] = DependantVectors( + vector1 = V1, + vector2 = V2, + tolerance = 0.05) + # + # Verification par les calculs sans les routines de scipy.stats + # (ref numerical recipes) + n = V1.size + df= n -1 + # Les routines de scipy.stats utilisent une variance calculee avec n-1 et non n comme dans std + # t = (m1 - m2)/ sqrt[(varx1 + varx2 - 2 cov(x1, x2))/n ] + # ou var est calcule comme var = somme (xi -xmean)**2 /(n-1) + var1 = numpy.sqrt(n)/numpy.sqrt(n-1)* V1.std() * numpy.sqrt(n)/numpy.sqrt(n-1) * V1.std() + var2 = numpy.sqrt(n)/numpy.sqrt(n-1)* V2.std() * numpy.sqrt(n)/numpy.sqrt(n-1) * V2.std() + m1 = V1.mean() + m2 = V2.mean() + cov = 0. + for j in range(0, n) : + cov = cov + (V1[j] - m1)*(V2[j] - m2) + cov = cov /df + sd = numpy.sqrt((var1 + var2 - 2. *cov) / n) + tverif = (m1 -m2) /sd + aireverif = 100. * betai(0.5*df,0.5,float(df)/(df+tverif*tverif)) + if (aireverif - aire < 1.e-5) : + print " Le calcul est conforme à celui de l'algorithme du Numerical Recipes" + else : + raise ValueError("Le calcul n'est pas conforme à celui de l'algorithme Numerical Recipes") + + if (numpy.abs(aire - 57.99159)< 1.e-5) : + print " Le calcul est JUSTE sur cet exemple." + else : + raise ValueError("Le calcul est FAUX sur cet exemple.") + + print + print " Test de Student pour des vecteurs independants supposés de même variance" + print " ------------------------------------------------------------------------" + # Tirage de l'echantillon + V1 = numpy.matrix(([-1., 0., 4.])).T + V2 = numpy.matrix(([-2., 0., 8.])).T + V1 = V1.A1 + V2 = V2.A1 + # + # Appel de l outil IndependantVectorsDifferentVariance et initialisation des inputs + [aire, Q, reponse, message] = IndependantVectorsDifferentVariance( + vector1 = V1, + vector2 = V2, + tolerance = 0.05) + # + if (numpy.abs(aire - 78.91339)< 1.e-5) : + print " Le calcul est JUSTE sur cet exemple." + else : + raise ValueError("Le calcul est FAUX sur cet exemple.") + + print + print " Test de Student pour des vecteurs indépendants supposés de même variance" + print " ------------------------------------------------------------------------" + # Tirage de l'echantillon + V1 = numpy.matrix(([-1., 0., 4.])).T + V2 = numpy.matrix(([-2., 0., 8.])).T + V1 = V1.A1 + V2 = V2.A1 + # + # Appel de l outil IndependantVectorsEqualVariance et initialisation des inputs + [aire, Q, reponse, message] = IndependantVectorsEqualVariance( + vector1 = V1, + vector2 = V2, + tolerance = 0.05) + # + # Verification par les calculs sans les routines de scipy.stats (ref numerical recipes) + n1 = V1.size + n2 = V2.size + df= n1 + n2 -2 + # Les routines de scipy.stats utilisent une variance calculee avec n-1 et non n comme dans std + var1 = numpy.sqrt(n1)/numpy.sqrt(n1-1)* V1.std() * numpy.sqrt(n1)/numpy.sqrt(n1-1) * V1.std() + var2 = numpy.sqrt(n2)/numpy.sqrt(n2-1)* V2.std() * numpy.sqrt(n2)/numpy.sqrt(n2-1) * V2.std() + m1 = V1.mean() + m2 = V2.mean() + var = ((n1 -1.) *var1 + (n2 -1.) *var2 ) /df + tverif = (m1 -m2) /numpy.sqrt(var*(1./n1 + 1./n2)) + aireverif = 100. * betai(0.5*df,0.5,float(df)/(df+tverif*tverif)) + # + if (aireverif - aire < 1.e-5) : + print " Le calcul est conforme à celui de l'algorithme du Numerical Recipes" + else : + raise ValueError("Le calcul n'est pas conforme à celui de l'algorithme Numerical Recipes") + + if (numpy.abs(aire - 78.42572)< 1.e-5) : + print " Le calcul est JUSTE sur cet exemple." + else : + raise ValueError("Le calcul est FAUX sur cet exemple.") + + print diff --git a/src/daComposant/daNumerics/__init__.py b/src/daComposant/daNumerics/__init__.py new file mode 100644 index 0000000..6bcb582 --- /dev/null +++ b/src/daComposant/daNumerics/__init__.py @@ -0,0 +1,19 @@ +# +# Copyright (C) 2008-2009 EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# -- 2.39.2