From 3601eb92eec39706d25e02bdb5a4b5f30d840a1a Mon Sep 17 00:00:00 2001 From: Luc Maisonobe <luc@orekit.org> Date: Mon, 10 Jan 2011 08:11:44 +0000 Subject: [PATCH] completely revamped partial derivatives computation in numerical propagation added a way to perform numerical propagation in either cartesian parameters or equinoctial parameters --- .gitignore | 4 + checkstyle.xml | 20 +- findbugs-exclude-filter.xml | 31 +- pom.xml | 28 +- src/design/OrekitForces.png | Bin 0 -> 8678 bytes src/design/OrekitVariableThrust.png | Bin 0 -> 11477 bytes src/design/attitude-class-diagram.png | Bin 0 -> 33375 bytes src/design/bodies-class-diagram.png | Bin 0 -> 12819 bytes src/design/cartesian-class-diagram.png | Bin 0 -> 16939 bytes src/design/data-class-diagram.png | Bin 0 -> 13104 bytes src/design/frames-class-diagram.png | Bin 0 -> 25254 bytes src/design/orbits-class-diagram.png | Bin 0 -> 23939 bytes src/design/orekit-packages.png | Bin 0 -> 29050 bytes .../partial-derivatives-class-diagram.png | Bin 0 -> 21022 bytes src/design/propagation-class-diagram.png | Bin 0 -> 44744 bytes src/design/sequence_diagram_propagation.png | Bin 0 -> 11621 bytes src/design/time-class-diagram.png | Bin 0 -> 25568 bytes src/design/tle-class-diagram.png | Bin 0 -> 7119 bytes .../java/org/orekit/attitudes/LofOffset.java | 6 +- .../orekit/bodies/AbstractCelestialBody.java | 14 +- .../java/org/orekit/bodies/CelestialBody.java | 5 + .../orekit/bodies/JPLEphemeridesLoader.java | 18 +- .../org/orekit/bodies/OneAxisEllipsoid.java | 2 +- .../org/orekit/errors/OrekitMessages.java | 10 +- .../forces/AbstractParameterizable.java | 89 +++ .../forces/BoxAndSolarArraySpacecraft.java | 7 + .../java/org/orekit/forces/ForceModel.java | 4 +- .../forces/ForceModelWithJacobians.java | 47 -- .../org/orekit/forces/Parameterizable.java | 14 +- .../orekit/forces/SphericalSpacecraft.java | 39 +- .../org/orekit/forces/drag/DragForce.java | 27 +- .../gravity/CunninghamAttractionModel.java | 20 +- .../gravity/DrozinerAttractionModel.java | 20 +- .../forces/gravity/NewtonianAttraction.java | 127 +++++ .../forces/gravity/ThirdBodyAttraction.java | 28 +- .../gravity/potential/SHMFormatReader.java | 3 +- .../maneuvers/ConstantThrustManeuver.java | 35 +- src/main/java/org/orekit/forces/package.html | 5 +- .../forces/radiation/RadiationSensitive.java | 9 + .../radiation/SolarRadiationPressure.java | 126 +++-- src/main/java/org/orekit/frames/Frame.java | 2 +- .../java/org/orekit/frames/FramesFactory.java | 2 +- .../org/orekit/frames/SpacecraftFrame.java | 2 +- .../java/org/orekit/frames/TIRF2000Frame.java | 2 +- .../java/org/orekit/frames/VEISFrame.java | 2 +- .../org/orekit/orbits/CartesianOrbit.java | 233 +++++++- .../java/org/orekit/orbits/CircularOrbit.java | 108 +++- .../org/orekit/orbits/EquinoctialOrbit.java | 89 ++- .../org/orekit/orbits/KeplerianOrbit.java | 322 +++++++++-- src/main/java/org/orekit/orbits/Orbit.java | 75 +-- .../orekit/propagation/SpacecraftState.java | 4 +- .../events/AdaptedEventDetector.java | 51 +- .../orekit/propagation/events/EventState.java | 6 +- .../events/GroundMaskElevationDetector.java | 16 +- .../AccelerationJacobiansProvider.java | 51 ++ .../numerical/AdditionalEquations.java | 71 +++ .../AdditionalStateAndEquations.java | 82 +++ .../propagation/numerical/Jacobianizer.java | 282 ++++++++++ .../propagation/numerical/ModeHandler.java | 11 +- .../numerical/NumericalPropagator.java | 374 +++++++++--- .../NumericalPropagatorWithJacobians.java | 530 ------------------ .../numerical/ParameterConfiguration.java | 77 +++ .../PartialDerivativesEquations.java | 454 +++++++++++++++ .../propagation/numerical/StateMapper.java | 59 ++ .../numerical/StateMapperCartesian.java | 87 +++ .../numerical/StateMapperEquinoctial.java | 74 +++ .../numerical/TimeDerivativesEquations.java | 294 +--------- .../TimeDerivativesEquationsCartesian.java | 114 ++++ .../TimeDerivativesEquationsEquinoctial.java | 280 +++++++++ ...TimeDerivativesEquationsWithJacobians.java | 90 --- .../java/org/orekit/propagation/package.html | 4 - .../precomputed/IntegratedEphemeris.java | 52 +- .../sampling/AdaptedStepHandler.java | 64 ++- .../sampling/BasicStepInterpolator.java | 9 + .../sampling/OrekitStepInterpolator.java | 10 + .../localization/OrekitMessages_de.properties | 24 + .../localization/OrekitMessages_en.properties | 24 + .../localization/OrekitMessages_es.properties | 24 + .../localization/OrekitMessages_fr.properties | 24 + .../localization/OrekitMessages_gl.properties | 24 + .../localization/OrekitMessages_it.properties | 24 + .../localization/OrekitMessages_no.properties | 24 + src/site/xdoc/changes.xml | 27 + .../attitudes/LofOffsetPointingTest.java | 2 +- .../org/orekit/attitudes/LofOffsetTest.java | 2 +- .../org/orekit/errors/OrekitMessagesTest.java | 2 +- .../java/org/orekit/frames/FrameTest.java | 4 +- .../TODFrameAlternateConfigurationTest.java | 4 +- .../java/org/orekit/frames/TODFrameTest.java | 2 +- .../orbits/CartesianParametersTest.java | 76 +++ .../orbits/KeplerianParametersTest.java | 67 ++- .../propagation/SpacecraftStateTest.java | 5 +- .../EcksteinHechlerPropagatorTest.java | 2 +- .../propagation/events/DetectorTest.java | 2 + .../numerical/NumericalPropagatorTest.java | 96 +++- .../NumericalPropagatorWithJacobiansTest.java | 347 ------------ .../IntegratedEphemerisTest.java | 3 +- .../org/orekit/time/AbsoluteDateTest.java | 2 +- 98 files changed, 3772 insertions(+), 1760 deletions(-) create mode 100644 .gitignore create mode 100644 src/design/OrekitForces.png create mode 100644 src/design/OrekitVariableThrust.png create mode 100644 src/design/attitude-class-diagram.png create mode 100644 src/design/bodies-class-diagram.png create mode 100644 src/design/cartesian-class-diagram.png create mode 100644 src/design/data-class-diagram.png create mode 100644 src/design/frames-class-diagram.png create mode 100644 src/design/orbits-class-diagram.png create mode 100644 src/design/orekit-packages.png create mode 100644 src/design/partial-derivatives-class-diagram.png create mode 100644 src/design/propagation-class-diagram.png create mode 100644 src/design/sequence_diagram_propagation.png create mode 100644 src/design/time-class-diagram.png create mode 100644 src/design/tle-class-diagram.png create mode 100644 src/main/java/org/orekit/forces/AbstractParameterizable.java delete mode 100644 src/main/java/org/orekit/forces/ForceModelWithJacobians.java create mode 100644 src/main/java/org/orekit/forces/gravity/NewtonianAttraction.java create mode 100644 src/main/java/org/orekit/propagation/numerical/AccelerationJacobiansProvider.java create mode 100644 src/main/java/org/orekit/propagation/numerical/AdditionalEquations.java create mode 100644 src/main/java/org/orekit/propagation/numerical/AdditionalStateAndEquations.java create mode 100644 src/main/java/org/orekit/propagation/numerical/Jacobianizer.java delete mode 100644 src/main/java/org/orekit/propagation/numerical/NumericalPropagatorWithJacobians.java create mode 100644 src/main/java/org/orekit/propagation/numerical/ParameterConfiguration.java create mode 100644 src/main/java/org/orekit/propagation/numerical/PartialDerivativesEquations.java create mode 100644 src/main/java/org/orekit/propagation/numerical/StateMapper.java create mode 100644 src/main/java/org/orekit/propagation/numerical/StateMapperCartesian.java create mode 100644 src/main/java/org/orekit/propagation/numerical/StateMapperEquinoctial.java create mode 100644 src/main/java/org/orekit/propagation/numerical/TimeDerivativesEquationsCartesian.java create mode 100644 src/main/java/org/orekit/propagation/numerical/TimeDerivativesEquationsEquinoctial.java delete mode 100644 src/main/java/org/orekit/propagation/numerical/TimeDerivativesEquationsWithJacobians.java delete mode 100644 src/test/java/org/orekit/propagation/numerical/NumericalPropagatorWithJacobiansTest.java rename src/test/java/org/orekit/propagation/{numerical => precomputed}/IntegratedEphemerisTest.java (97%) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..1b74f44447 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.classpath +.project +bin + diff --git a/checkstyle.xml b/checkstyle.xml index 3903d294b9..2e039e229e 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -11,16 +11,15 @@ <module name="FallThrough"/> <module name="FinalLocalVariable"/> <module name="FinalParameters"/> - <module name="GenericIllegalRegexp"> - <property name="format" value="\s$"/> - <property name="message" value="spurious spaces at end of line"/> + <module name="Regexp"> + <property name="format" value="[ \t]+$"/> + <property name="illegalPattern" value="true"/> + <property name="message" value="Trailing whitespace"/> </module> - <module name="GenericIllegalRegexp"> + <module name="Regexp"> <property name="format" value="System\.out\.println"/> - <property name="message" value="forbidden print to standard output"/> - </module> - <module name="RegexpHeader"> - <property name="headerFile" value="license-header.txt" /> + <property name="illegalPattern" value="true"/> + <property name="ignoreComments" value="true"/> </module> <module name="HiddenField"> <property name="ignoreConstructorParameter" value="true"/> @@ -55,7 +54,6 @@ </module> <module name="RedundantModifier"/> <module name="StringLiteralEquality"/> - <module name="TabCharacter"/> <module name="TodoComment"/> <module name="UnnecessaryParentheses"/> <module name="UnusedImports"/> @@ -74,6 +72,10 @@ </module> <module name="FileContentsHolder"/> </module> + <module name="RegexpHeader"> + <property name="headerFile" value="license-header.txt" /> + </module> + <module name="FileTabCharacter"/> <module name="NewlineAtEndOfFile"/> <module name="SuppressionCommentFilter"> <property name="offCommentFormat" value="CHECKSTYLE\: stop JavadocVariable check"/> diff --git a/findbugs-exclude-filter.xml b/findbugs-exclude-filter.xml index 249f7a91c6..7ae26a4e0b 100644 --- a/findbugs-exclude-filter.xml +++ b/findbugs-exclude-filter.xml @@ -63,11 +63,40 @@ <Bug pattern="DLS_DEAD_LOCAL_STORE" /> </Match> - <!-- The following equality test is intentional and needed for semantic purposes --> + <!-- The following equality tests are intentional and needed for semantic purposes --> <Match> <Class name="org.orekit.time.TimeComponents" /> <Method name="equals" params="java.lang.Object" returns="boolean" /> <Bug pattern="FE_FLOATING_POINT_EQUALITY" /> </Match> + <Match> + <Class name="org.orekit.orbits.KeplerianOrbit"/> + <Method name ="eMeSinE" params="double" returns="double" /> + <Bug pattern="FE_FLOATING_POINT_EQUALITY" /> + </Match> + + <!-- The following internal representation exposure are intentional, + They are used to pass data back and forth between classes + --> + <Match> + <Class name="org.orekit.propagation.numerical.AdditionalStateAndEquations"/> + <Method name ="getAdditionalState" params="" returns="double[]" /> + <Bug pattern="EI_EXPOSE_REP" /> + </Match> + <Match> + <Class name="org.orekit.propagation.numerical.AdditionalStateAndEquations"/> + <Method name ="getAdditionalStateDot" params="" returns="double[]" /> + <Bug pattern="EI_EXPOSE_REP" /> + </Match> + <Match> + <Class name="org.orekit.propagation.numerical.TimeDerivativesEquationsCartesian"/> + <Method name ="initDerivatives" params="double[],org.orekit.orbits.Orbit" returns="void" /> + <Bug pattern="EI_EXPOSE_REP2" /> + </Match> + <Match> + <Class name="org.orekit.propagation.numerical.TimeDerivativesEquationsEquinoctial"/> + <Method name ="initDerivatives" params="double[],org.orekit.orbits.Orbit" returns="void" /> + <Bug pattern="EI_EXPOSE_REP2" /> + </Match> </FindBugsFilter> diff --git a/pom.xml b/pom.xml index 82b34be574..48600ae829 100644 --- a/pom.xml +++ b/pom.xml @@ -133,7 +133,7 @@ <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> - <version>4.4</version> + <version>4.8.1</version> <type>jar</type> <scope>test</scope> <optional>false</optional> @@ -144,7 +144,7 @@ <plugins> <plugin> <artifactId>maven-assembly-plugin</artifactId> - <version>2.2-beta-4</version> + <version>2.2-beta-5</version> <configuration> <descriptors> <descriptor>src/main/assembly/source-assembly.xml</descriptor> @@ -155,7 +155,7 @@ <plugin> <groupId>org.apache.felix</groupId> <artifactId>maven-bundle-plugin</artifactId> - <version>2.0.1</version> + <version>2.1.0</version> <extensions>true</extensions> <configuration> <instructions> @@ -181,7 +181,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> - <version>2.0.2</version> + <version>2.3.1</version> <configuration> <source>1.5</source> <target>1.5</target> @@ -190,7 +190,7 @@ </plugin> <plugin> <artifactId>maven-site-plugin</artifactId> - <version>2.0.1</version> + <version>2.1.1</version> <configuration> <inputEncoding>UTF-8</inputEncoding> <outputEncoding>UTF-8</outputEncoding> @@ -199,12 +199,12 @@ <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>cobertura-maven-plugin</artifactId> - <version>2.3</version> + <version>2.4</version> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>findbugs-maven-plugin</artifactId> - <version>2.1</version> + <version>2.3.1</version> <configuration> <threshold>Normal</threshold> <effort>Default</effort> @@ -214,22 +214,22 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> - <version>2.4.3</version> + <version>2.5</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-checkstyle-plugin</artifactId> - <version>2.3</version> + <version>2.5</version> <configuration> <encoding>UTF-8</encoding> - <configLocation>checkstyle.xml</configLocation> + <configLocation>${basedir}/checkstyle.xml</configLocation> <enableRulesSummary>false</enableRulesSummary> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-changes-plugin</artifactId> - <version>2.1</version> + <version>2.3</version> <configuration> <xmlPath>${basedir}/src/site/xdoc/changes.xml</xmlPath> </configuration> @@ -244,7 +244,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jxr-plugin</artifactId> - <version>2.1</version> + <version>2.2</version> <configuration> <outputEncoding>UTF-8</outputEncoding> <linkJavadoc>false</linkJavadoc> @@ -253,7 +253,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> - <version>2.6</version> + <version>2.7</version> <configuration> <overview>${basedir}/src/main/java/org/orekit/overview.html</overview> <links> @@ -283,7 +283,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-source-plugin</artifactId> - <version>2.1</version> + <version>2.1.2</version> <executions> <execution> <id>create-source-jar</id> diff --git a/src/design/OrekitForces.png b/src/design/OrekitForces.png new file mode 100644 index 0000000000000000000000000000000000000000..c51a7e9b3eb634e7ae520778cd9679bf1e5e19c1 GIT binary patch literal 8678 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 KkeMc@uKW*ub+Taq literal 0 HcmV?d00001 diff --git a/src/design/OrekitVariableThrust.png b/src/design/OrekitVariableThrust.png new file mode 100644 index 0000000000000000000000000000000000000000..f1e5803d61fcbd1986a40f620fbca1a9c935d671 GIT binary patch literal 11477 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 Nh^hI(yaVTM{vY=P=Z^pY literal 0 HcmV?d00001 diff --git a/src/design/attitude-class-diagram.png b/src/design/attitude-class-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..2ae9126259b635ad1fb3a400cff40a7b0dd748a9 GIT binary patch literal 33375 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 FzW`wub>;v7 literal 0 HcmV?d00001 diff --git a/src/design/bodies-class-diagram.png b/src/design/bodies-class-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..c866cd3503861bfbaea9222b1888434d044ed5ff GIT binary patch literal 12819 zcmeHucT`i|wl9i8KuQE@(u@kIbd+8{KrECfDj?Me0!mX_AORv8Kmp-HP>_x&NC`w~ zQUVx7dhaD5AiadpYjSr0zvDUg+;i_4<Gu0Tc;o%U*zCRLT)*|3bImo^Tr1D78C+s! zJkCf%L&JRKvbG@&4Xqdr&4De@LEufw<xqDT8tJ|(+JD^iik~C+W_#SW5L}2mwj6Fd z_(Avq{|n|$$cXMTYr9;kCwAm<vx-!Y&NVGeJ~M<Aa3bNt<YjIdnf!p`JXy708!8`T zzA2n(>M(t08gltU#<Rj;r-Jq~O_%#GVO32XOUFi58vJe}F}UtQhdE!h3ZKQF1HNnh zw`bt1<UzOj0mUuU)wA!T3?Hm0iTWPgeM~pej4=eBZu+y~qo@yzQQ$P{!>wer6wvf) z#z7%8Rpu(k8HgVBAyb0^wkvx0D(D9F;oP-fDq^|uIb|b9dT?bJs*a4&SOGi>YCUT< zW>=@UsV4W%uGR;wZ1_nLKyOF<IkKowdg}5`XzorBHZL!)3|U|L^?_3f=9OWlm(!$9 z@%^}RvpIEifRIoo`*W)aIYJizuFTUdHG3e9Xrvhf?s92&!bBNXImpY`)j4fC7f`^S z5%Eoee5gdP)pU<CS9mRHv9j>Wk(r{YLZho0BL#~u7Hv>#w(amN{y=O<kNfsm3VVeq zYoI{2%c~OXB6-l=O!WOtQs%Ty@o2cOoNrN|8E9rajsbGkHeexk*mpJ2&S@V<X32F% zm`7KRrisg^D~vFlpz`YKJ+H<EHCj8faQ&hJfVW&j!Aq6{<_9a(+e>XGySk8+VDm|5 z4he|Z>WiFr76EHK3jGMjy#b5+raBU|O+TJpw3UZtbp+@RjZ?e71)51%yblp`%}Rme zPp(-x9aMHK+hs@`k!n6++pRh>3Gc4xrH;M`+?BA{S{~{hRJitt=x}13zi*Sh2R6FL zQ8Dk>cDQet=(w$XzO7f%%)la5KMZs#WFKs-R!@uC4LHZ3-490srb!o+I+WILdiH&A zH;Y=>>`sS0p6tt~coVSXJ_C&8yM7J{UvqW|6@`TQ#W_0wPGRXBHE)zEgl^rpPW_8E z!h?LY)aJfvi?5fMy?71Qso3jRQvNDDi|Q5oHX75Ja+Wpi48Sf7J&P+$nqM5Gq@60W z!GFA4&YO2-JvUEJ`P+wmQHUcy;3J(J_=6~Y30b?-hf@bBp70dw#@^vctFQI;d+wnN zGOGq0h9dy`u400>BCEz)p9R*--jkm$2{_`-TS91zHQ*7)wR$GEGzb;fxG0w<N@mu` zxYC-6tMVnb#K_o(2oil8)%~;%J4fNVmu3ayLdmiE8E`&fRA&1YL4OEScpQ%(wzW6* z$Kc<HVJS7T)}{~ek-_TMGWYpS;l1}`VxmLT+t6l;Jeeq}_&#ytC%CH=4`3@mV`Jn! z49^?E)iWU3a0=<jjjj8diy<xpH}^@!$=9{US>zO@;o&7IX0`d&7@l!7Ry_luX_94% zdlmZ@FD+5_a8l1O$;&2DSM{%6AA&;4+C7Ny)V0Pgi~II21Wd`t>^0lt3Xzo<6<cE* zl0{{Pe*h!Lj=wvukS6){2(mxFzjxS|B|-0JMp$`{PLR+q2ibs#jTdrwTY-YGtvi=; z6cKs4eYbvo1l%Z78XZufmyq(XB2YOMU(VMUP`vNf9xhR-E+@ZPt<rW=I>4N6W^a}x zG}g%i#iEc|h5~nxG9QjXF}B%s<{KLO?(`s%g%HLRe`S5SB=<$DLXXmGYWOI)^&Q-m zS(z|7QeTOG7<xpKPbspJaQfj5=ef)^vH~I-V1Jq!7R8U-XPN;3dTDKeaai@xTN8(| zR^RVKPYt1xWD?7~k!H`h_yzZUQis8uWSO(pD-wcz=lmKgh5gSn47_{k$$_G(e82#c zX0MHXTDTWGWRK%nM7BfZam~2WiFrolQnOpf@E_{-riif~=+txYDv1s6x$pJ5pn3=U zWq_N%dA95(*V1z??jk_<e={5E`0!0i9%W_lu@tAg=Jv@Au?O}xGHMwyW2F7LhsJt1 z0#p7nXvR<RI6hFW;fysX4m+m_y#|El45fXS7X@72Mcig;ATqzR4SJjU>C?WqGo!I7 z;R}dpIG3f0fYMze<1j8D8V$9cJaqT9yFAoXAn&2zZr~6#1}P)&-s_#1Oejk94eMJb zWUhkc@=Y5?wM3#0$xYct$K=iVs+2s$*PxS(rz2l@7wp@9j2R}~J=bMKDs8;?d{^?p zd#eT)edq~KZ8<9&Y)rBD49`VpUaxuFsYpbVTjI_2+WEA-;C2+RBT4Y?e&d}v)3Z!? z&<n>0mh%o7o_~5Vr}68IpfTLL%`H^1=sxt^R8-;M?!wT;03*pKFZh*Qn#}=Q3b;A@ zC+HPUmA(QO{)oF$?aR5rXC3xx=JZ#Qiv7hv6POzC&>VK}e&$>^pZYkTq4+yM9Jr-r z0)MF!=V<#QrkyWRFkC_p;qOr)Te652Db3qkM*6BF>@LaLCB*b=d#NwEsImjLxTOnn z(03{tzH16;k#3JY<#RPP{b;Xe1QY{b4lEDl`C56_3AZ~q%*G>|2Ao)#!4Eq53BOo( z`FLPK5~cK?{1qWFOOI?`S8D~CAt->u{uOVH*4a631z;<cEv2hf8-aoS3Zc0kWRsZ| zcxiFE{OQAma{|uGCem9+@nH@KkoDSPVXWcRgqs#5=c;pnI|3`U0oJ)>Dowb|3Wq3I zetzbCR3EUgYxdYPz&@_XS}n=ygOES`>8~a|0~T`TD6lXZh}N8D&0gQnJC|>a+o{gn zeh<|KR&ct^_1viVu(lhjI5hS{31Vfspxbh5GBw=Ntx;Aj;^x(}vZht3wNqzQJKJ?Z zA(`}oJ`0Ou^^H$;qa|d+b(6w?WTW8n#^Fg>>Q`a^1iM^%0k0n_FR01JR#v}D1&#qW z=xmaX#*&mMVvF0=Pe!YOyrZLO`n8#J@y2)@j({~_fQ@>PZvXVQC=;co9&;HIeh2#c znP;NFa+a8m&N>^agxd57-Nam=$5jw-#{5M1F1UjFk{ZiB^%2uf+Uh5%Oxy<Gtmp!* z2Fu1S9RhGDi&oI!{1{*<$2YH6hJyfQo<@EL5IbymO%D4%_Js5Sp_4-BfOCK5Z$C>0 zvMl*AuH=xLt{_oz#WT|-ihzflQE&JM03F84??B0AeFj*N`gSLy_)f)w&uU&0;av{A zY?MDl;36<`)8g%e?ip<OySmgQtDt`INXz@Hpb*T2D$yITOn?7i;pBB{j`gZuQX@K( z2{OA~l$s`#KI$|vYZm}seZp0itcWWr8x#Mh`nFz{bY%x?_UP#bVO`b(UF;23^j#3` zZ!cX3hQ|0BTxdJS1S4pBY}<}c3S&AEMf`okjF8zXNxw&KEl45`-kJ*VWOx{eFnON> zun#{>`wj%(h?rr;1qkn^i|46dWKev?lcOnXmAgsbIb8;rgHb1B_H!_bZ(}jKqD*np z!E!0~V)$ENARJ;4$Hq)0^h)`t?DFfLHppz4^1OYR*BSvXi8w~hJMRGEuZI{y#Ho2` z$2_%z?e2(=9uo<0IeI6u#qt>d`^_(G_%1O@|J$CDt@+Zp^0Lt|Wlq&#YC?-eW2r9o zn>ZTVgJ^WHY+oL$w<0h8h6CVzW)Mkq+FS%iO!|!#U~=rH!+pL0n64<WXKXpS3+l4D zn2RD=o$(W<!Tz}fHrl?hdQJ;4dz$f47oSnMyzj=7+)~W?8=6W@1O;^LHVD>Epj4{r zTlvj(`+1PgA9x%9g4G|zr?KN{dQ>5fikdy{E<0bk0l|R@zk9zm*op`$&z-6_bJ_Vc zVt0_{$Wi?3Q)qn=^Z_hVv&R*Uof1H7sXJ^gzG*Hi-t7;K9RqsX>42*CERR|3I9T1> z)}y%t*=vlJqVa@+%)-EO31GU)d4^pK$mV9s!}e&a^#G_SbwJ=fJQWKetg^lv%jqc3 z-|+kfn7B122oRB{zLgi8#6Tmay{ni&?>CyDZ#C#On$D@tPGj;6T*wD;RDv))Ca~{f zhYR4<cG}h7|BiOvT777F%%j`l1_1VP;~p^z-2;p;&3+J!odFhSv|VsjD7fh4R&sPq z3#~C4TdTGmu_g<dt%dp-lFq}PM9!Io-KTYJ)dj&EmS%$cJ?ebx4?Af{AQ&J_cc;u) z`X2kp5p=zW^c8xGL=4WLIVh?xna>VJd(_Fu*g>;nUQGx}_$A~SjHwPZ>~vQiL7RIH z=w6YW6St&yRm6aed_K#6(GZuH!So#Ext+#;#*dW%mfJY=8mXfNedud>;^(JPOE&yT z1{n8;S30)O<zIhtEGPyWt%_fe1iSXSOY&P?V4-#SAecK~;p#PFHmc<yEIz;oVWKT) zt3l^-5kjuRBP*4-jvy;S)$7pg3YOwN0}Ch6aJA(_)`3FX?~I&jKVvbLC0+*vP~MZ~ z8ozcg51X=(zM4JS-s#@s7AfQYeUW3DCh~^bYGGDhljem7kphhp{-d7Fu*m__Q-30o z?`v@-KI7=4k#PJFEn$hk2wgd1Z3N2Y8MGsM8ySGiv<>Myx+Hi@Bpe-V4z7`FGv4yP zI%vi(`Q}e!+eDkS$&3lPjqE^!w9CAe0hr~s+g6_x{V*>iW_|C&i<Fqm4<VpuAM)_@ zWol~noP3#9QE1s)=K*s&#r}DR^<reqI$PeYH9o`cq$LF`Wst;Up}wLQ=xO<m`3K$z zF_kUZ#aGdFl55pDF+Xc}M<ot%d~1YS9y}IqJTIYWDbOQ#9~MAcATm?NE|;&OLa5&P zGxqx3PxbND(#<Cqea6)zHgb<s3BM+3QRZW+`;zm!OD94+eI7OL;G?f;lHJu2h!jer zoGiL(p-{0Q=Wb!r>xz1N(xv=OSy3g=_ar^|QVKuIi-vqVe!r7AksF!JKL!dBQzM=5 z?J?5RE_|!*mzL2ke}p{itQ&b(Wz=Azy(+f_cb+D77l>?j8}sigx=1l`xC3iY(&D8v zn#Y+ZdOVo?Sz1pU#Ea0mtz+vRkP>;ehwfnu;H=V3n=c!0OBqxRtd^MZZ#s+M)<PGY zT<x>o(?3`X)yp(5PS1>NrTh6d8jSzm$O_Zi(j(mRO7hRMuuJxAu6dU!;(t$tXV1~a zuFCi79f3udy(qWi9HcZTznFOv@y2B`p^ra4RPF>94h`nadgY>{Wk?JPCCHYBBWbOO z)nT};<@@!np-l8n8fw6F3Eg43DS8O%wREa%(eqMZX#SU4#%(eFo_FwmH|pn~{)`n_ z#tWnADOT@7)P*3S*LCy%a2b73zLAh$RHKjYmsoQ%_&)R9H9Gm7g9ZE<TB-ys^^*Ra zk5H`7VJ1iJrL#^y?sD~H!YNuuZyb4a<{19v;Nv&!_>;!<WVfb5&7Nx?7?VQ5I?-UA zMU8-5?OrVtQCknd{(MUBTQNFX+~z!f{-NX(M-3=iM@mbXDdtBvZd6aQO8KkWQ4~cM z76nx)tzSab%}u5YoZSUzaZ&C@)f{<?+h414-MTW$g5oHlUA+^3GN?Yh2<k6t^=Gtt zF1+l#E~JEDos{51^rp{eddQeQ)50<b#;T`TSaTZmxcw*{)*o4m&%d?ys&mUU&W)Vv zU{GGsRMzFuorJn_Uo~Ouq;4Bcp)F*J=?2uR37u2i4AJDSrR6AR8!M><b{fIA3B=X; zKS0*ygL0EjHI{B{hId?WGu1@#l~TKe*wuF@!{glhBI(^a8UWIE-b6<++=iK!#YD!V zu~JmhGf$%rcOAo<G&O{u$_v6)CL$}4Wj@P?4kG7$M<T864ja2~KYyXNqbT3~rn_W* zGb9jO*-@SC_7S^Qzn-e6;6K`luV!Y|^qP>Se<Aq3mTHsC{?GoB6EnWjMV3rjtOx6n zMbj1#KO2R^z3O3lrV5*wR$^ec`La-#1^0%w>kG4nm~Two`4;yo-i3~bGlY+Fc?PLs zCkAWSyk!V(+)70?M?tymO3g9VZXlSd1>0crb_!JWYVvb8ehJrP2~TdyJ<e*$@cHbO z9JveT{sDxY^f0FMJDR*`7rN9VtX~s)w?1#3@PlnnF{^%AXtGYcyqy<f&aJR?e~DWi z<iEno5rj<<wITjd%GF=F841lsz8wCe#-k~s*+B*()^SpLrdl_)tVTFY#kp;hBM|H1 zsfG}PgnsRiQz%F2nRqFbI4#c?7X&RB;o`o<UzvG(taz!CTxwPz2kq-`!KXYmi20_N z5V`ZRt)EqTlt*#gAA<-|y}zRvPP-+jdTm;<I`k)Bx&U4{bTmH&N;voFd9;H21CZ|K z{2{`;)gQ!<XepMy6G^gGt;Ggji;8Q`rA^8(t@^LfiS<lgK_i{;m-mc;IMuCKJ&R(2 zh-DrLLYyfx$g)Q8o*6&GZt$a}J9l{J+nV;B7T1+~2?M^0_D}UZeNvtk4J3WwE_oUX z#B_yWQhwwVZix9sWj^g;mg5^`U9<i-7+~kqrEG_XpBukjNDLPxpAMI2a8%9{F+fKQ zCo|XBwW@b1A?iW7=ksBA#yYmTTRz@pk#|3s3bk?n_EIO~GAjsnD*UT>C$eRpkG?C( z=Pn_#p_5$%U-Phq?q_PW;mV+;ygrB**cp@T?Grk#388N_{rWa1=FgADwt_h=1)QIa z^1T*PY#dUt1|erjYlCo#YP1BwwL8nIgzU&U99;VcuT*w1w_NnMaeR-tfOS{5E~zOj zbG_EaeHt<=MfA;R^|_&f_9*qt5C59y7J<vDAjiI|Z)Ev=*LBk2DQYEhO<wZD_|V9N z&h3ooOVP)GQpls#Z_^forrdc=ErI^8XrE`oleDUX70uf;5?ogq!<o|4I_dORH{U@K zbqVmn-fhvKM-q!qrd~b)d%C{VjG2|aAQPWGDyIOwLqv3!#@3rI<?`w1uCw(mv%Qn+ z<bXJ~;~8dsl5KKRBF_4;9D}#gRWr<%{get^w!CR!2*w-Kw9rQr{7X#c*wuz><p&<l zeT0&Kru%f+5xizIpgRqcn{S&{SF5bHm(83vM8%tZ=Z)2f-pwkj_zK@GdEBH6|JJyR z-*CO<>RanvuPWVn#ENb>VW|(b5Ge1{{gFr^mkus=b@Ok1L6J9u1dH1iiGw>@L~?Nv zKa!HGvM5L%h4LYnD|TjyJ`1YVIb?E0w<e#m%TworkzWzM>!*kgxU}OHWPcO^F}2gS zHQ0*qmLrcr2f7g)lp3v}Zp26VdEDA7j00YsEJDG05bX1F#9u_0{hTq2POezeN9B3E zc`$&Oi7v>owIYvMwUtcDs!h0&4QKk+d6G!?F*A0fSD>42TwErAMWhgP(<4EJGdZQ> zS>@BZLkoe%<W2fPZ@(4CyH{tXd$r$mz0Z5DOknCblnOjCKry;@>{!$8MqzY8PMrQV z2MhT)sqPYcIse?agQZ-J!fJ&QvHIT}Eu_^+?CQhYuD$VBNN`NXw`&JWYhY0CI%d|g zsREHisiv&kZ5DIidDROFifk!_r*d{}N=4(2rO*^_ON?Eg%G&&6cX|v@ZC8(6Dk0&C zt)=Pt&ISv(X54LO3*NYF?<nP=Uh~Uw_4nPbuNZK12`^Uy#vjUAPryEl7`Cz4=rOB; z4|?1EVC(iNx;^2}_C0_V_0zXX<iokt3U;Gyrn58on%og3sUk1<&80VDV&Hh6linl1 zjzwE(FMounTuYDto)4z4VE-<K5(x!rs_gXBBd$xCrRA7)MPpf++5U^X>$#`y-<Jp^ z-&MPUmST#EfP8txyRHmt7zR8x&|gO8w0(hpMNG4e#90uG`^+h=C(gX<s7o?0bQv|d z>FR3!)ZqOy#8MOVRfC6z&3-Q-rZzjcp9|C8v$+g}go~08G%Puki6Q?$@ubjRNB(;$ z!fq$Pyr*UyQrC0>5A{7ZkF|Wl;B*Q-_XYrP=$~nQB_;E)>IhnInT;LsBN$xh0zd)p z_VdUT!lXXg?3gk#q45Y%wak{zfe&U~6Q-hi{9kvu%=W)=JpZ2Z_ftYKEpleeFtLvP zpeWU89bU0EJDa^xwEo7i&q&LpKy7&l^>M0xh@6lFY@^WBEeB0n36YG9jk8#CA)W8N z01?Y8qD-#I`+BdzQApBxh~AKE3!PNwdtIwXGngVd3a5IcK097!>$S_qcRRJEH}Zt# zjuAV}nDVh?<~uK|1yDqw(y|0pTI493+OEI~lGnUdmX6yZ5dHev)g}^%K5nE)h{mqg zTHm!Hw~fX3!Za858>dq?Co6Enfivoxt>NYK9=S;Jl8dOH+M!7*RN-RxDNzBRg)hGe z&<`&6O{h#1+Ult-r?6U}c0Bx4D{MDkKb3pzK9O|M5-21U_safK&sDzQJ+y@zLaK>b zt$8$~=G044*Uw68Q{ntMKay9owD|ldpQed{{NVciidBaSPc<?1rSs~e-AfK>E{gWF zm*Lcc4v=U7Ba5^0qkIJ#a)n>|o;%UK%haEf>whVc%cZ2}u77v&879@NuWZNe(#=+& z7NR1VblE@{eb24m2{npw@tyhS-^WPwl$mIk&%-6GehW`AUq~|>ooz*Y`3K%j`f++& zszt=>f$B%oQJa_~$_A9k5@yw$`<iNp6Bmhg5ffwVwfE6jmGYTaLyK+EerCjV0EE&m z2*c%l1v-EN-HK&h0l)R(K@!k`$6f~tyjdf5pA|Jt1WeuBmc{9vXpRY?$JXL1R@#ko z1}wHOfZC}*u0?@g4@qv1XeDhGql(w>gLgSfJ_8Y}VBP+%>{3R$5t2j)+qDJ{b<GG# z`U=!SH+EKu+XKw<=4QVXHT>vKm|({*ZtbvA+9H#--;JdIvFB2B5JxqL<55XuZ%A+w z?Y=5Bb^|SC5Vh-byA49wJD0`HA8kCz@EJcp!~tQVx#O$JO`~xD*x?OsKFvh|i;&^= z1)9e|<vir`T%p}|h~Q@8NcsVqn?T9t+mhA8sTVHW^VAndNL)(*6i(6_ncVHyyyjy^ zYNvS|02DZv09mizddr`e4$*k(f#QHF#ka5F<{Pz1KFRC%fjY`AESeK<z;a1oI-1i< zyNYP;n3E_~oUh`#fLKZs`Y`X51}Lirwb@ME^cVE<0F<BLl2<;6#s-&7OrX|}l>bNr zU}#7<dmaM8IJ~K=XGgl9Ewx>At$b--cqeIeblL(_>P-|*4KlmEe)eZyKbxIG64)q$ zvKU7hzo9l339wF6Y{LYT9-6igS$QbRju&>M8n9=Sh#QYpw*siBxd@8W104Zgi;r;R zdR-y80Uh*d_EasV_<G8#C}*auh&aX}gr!RvRY)nSp%O_x!u180J_3~_qBl04VTa~g zaV=Mo2KHGkpIyQOaU!9)Wih4N?h7C1lGmgeE}c+n0BkS4Y!xFbA4S`TiyqeML1R87 zy*cc8)*7UoGuRm@$4V@m9aW5jU$Vxt6T=?97Adetf?zhbw*D5TBMI1)PQ&PG^~A{Q z_<q~oTMZ^v@fzQMYjU;Za~1k=rEo_54Z-GK=#PQ%+3EFC!iiTi&!Jw1y7|>4BcL)< z%^rEL%vDi2v*wkZfT(Rip20o!E--|t=e?4aiF|GpLS-4#<#YxMfSv9!P>WpP8+#Bw ziUG<m`rKWW1_v9t{5acLxbilx5e<~*^M~2+k1b+-`h&}kCwZK+^@b<;t4JfRx)n0} z@Kj{Qh^XZU%?rCV*-Wi|?s-59akO85N$1dqrSoh<%6LOh+>aDXy@1L_OWlcnQ2O~^ zaYjUaMiBO4+1$?JLi)!H`6Ggo#Cf;;t<o8$1lY79Yr?~ZGl%7f1g-V6Hjdhl>pDFj zg%qF2izNhNo!2M*<iY$pS`97a(KSwmiS-1iviqHY`Me|HcJQUvFU>vAXkKN_jc|JF z+wzO><4wbnX6J>W=rwSj<_`^0>p61#DY)!1=G~Yk_rp5#0Z@+}NO`MWiTtS)-!huQ zjO<n!v@t!+IJ?ktMNUgJG|}xR8QH8-)|_J3hrnu|I(W0G>c!&(0RHUc&Y~1p#!zr= zUR`ZcdtPNR{t{HdXBjvLx3F7|pO8PPF-Fzz2I4h!jjy!@4rZ#g7=%6g2h+jDSEjER zC^yfyJI<X)?&}0jzz2~iAC>7wrQuhjaG87qul|^X@I*HvLs1tW!Z3A$PoeW7@6lw} z#wzB6X|lI)kkI3&g|JTn=c=oEK?4cB2!hejbICtJOUdZ&oLGzpKGg1hI2UvKPBef8 zjQ{ZDU%Y%g7a+BoxgK)hgff@>!L)*E4k9`t`d<hDef|$s|I3!al;+DH2J+L`@HMp! z2TOmB=sX7Na5RWvbtRmEcyRN}zx%RsRg)WeYrBf(&x8=(1MPjI!Ek&{{lBZ_-v?1e zu{(X!*%a9J{j>q5v+E-u?4>f=a`p%Te4?c?@BF(j?;s(exO3#YG&RnXBdeG-%B<Tw zVqFog+P00{fRcTGE@laOH&tJ-N_6Nt>qi!Q(1#=b8tVo3N2S}DL8uwHI-6&ImS5TW zM&6lyg9S=BN0-m=3vLTilxGW#%TxEiX*bP|R;=j)iThxEv}8g;0*AWY<Lmz5Rb&Mv z2zCWBJCf-IZ0mAbN}Ygn+*Cc~kBSo&--<az(ANVN8Z2#cXL2jZz?l-O+WHSpE5}L( zMl_bPL615nr--f+Bj$ijMEnA3<3bZrIkdMf0^H!v_cvM^=UVP~eRW!>qKi+~0Bt?} zdg-eX2u29N=y7@}W7ZloyYQour`GsDF#l7Q*>9rGhOpy5;7JX1pSqJ|%~vGFA8VaJ z6E>_-%P6wKY!-3*<2vmaa0O&&dfw)O`eC`}LvC%<`zhbG8yg1Q+x3fv{I<l42H0vW ztEnd#a>iU-xLk5m5rrg38N%A3Cc?j4U2IO|zEpBa#Y`TpPo8f{ej)I&Kx(058Ul>e z*0$NoQGUp0DK%V8sZZToG6XmnGO#dTjs>sI61^sShe)e-zST*pUR~7{j%26TDjnJE zC=S&lwh1X?TB3q<_<`Hn9WxSB0-|LTtrHx1e^V@o+{UdDP|fdki_B3w1w{nUnYbYz zD`2lPGQlMEV6w3DP<E?TgCH|3wb;gftP;QArxVU*a$W{Fcat^){LWn^Px`(id#=Re zI^h0@fet+J`13%4pNudqmx@~kyNvT*wl?$jKOiCj#@`K{S6?6vwZuv%LB7wH!Lw2l z)|`>tVK*X_S!XNOJDTr@w*rUbu8hr&@-Ljm4%5Ydeh0@RT%xOz@SIL#0#r6?-r#;I zlttqvzNZTU3vW*gv*w=bzfTG2FB+ue5m*J0RJnt|@yfr9LjN*U{P#iZ-xrS!;XS11 za1Ih0-ylZw4UDg8XgH|B1PN{UU;OVG9(+&Mh!Zo-N~z&F^Z}Z986ag#ruo1B&mHyH zBQg=eP)-mmJ9GUWTnl(Kd*{Y<jQ1q^<2l0JWRs{S>1&S9z~3|7byaC^ZDZ8r`V_bB zB!BqA0ST3zdX1o~Gmk28-MzS=Xf&Op)2%*pbJfiRu?=|+2rYW*<{)dU1{deCiT>w2 zKwW~AK|&!y**L3u;$6*(NW9&&qL?cAPi>I(EeB8C>E_8Qdb=_I#X50^0&w8~l#2%_ zoY#c}Ufh8)qos_5(4FV6k%DTrq>b8g@D41#2#zI%ZuTpx6AYtCHku&b*rA2R`NSaM zlO8)M=ZUIGgN?Kk*yVLCHc03#@#RN0KkAUwyJ`@x344A~SKwSjb^`2={Q2{C$Pm=4 zF@?yI;5Jwrthl*Ng1$PAzxzECxZaf|T(4%AXa+aNo%+#qkqSdL&rCuhx7tcTiEE(i zX-YN8?7c)hDn0i!+Jk5r!C>tkbbgIp?M*B1!rPPKCa2K}{I|TayL<+eI_IxQJQ9OT zJUD^=n5jr`?W&cp=IX5pkQPOEGZFLFREz~aga%Cx;t)h9t*paxNj@R`xMJZsr%AmL zt}wNNSLq7lKpuHr|9Da87DN1WzTykT*UDV7V7y7^Pv_##I1jGE*R0~RI92x|rDeh+ zqze;niz$9iiT2eR7zf25hvqB>OM?bitmRn6W)pI2sxb-Gf{5j)tNNsTFlXXsM|B3E z_rdF>DFS9s@2xMu6_n<Vy(@GcbmxeW5#kX0d;rdh1=GG`M4W0=`3Ni*<zu;WRT;ps zPPfUu0XyPSok#ZjYQdLOw<3q13X&Mf*a|OZmb`F{hqV=ltQuMh%YwTMCj`Nj^Zll; zqzMSEkT!3q42KsOn5kVzaqD7bwIGsD*`<`)?XxxJCtXp{1X~XtvdG)|?2!yuc8h7? zau?4B^B~Z`wNrv?EXF^al@LS~OA()BMG+PEC`BkL;jHH$a)0w*Sqk*#X`z>c_H2Ug zBWrKm?_zeo2`ZI+0voB*?ppS}*)-3G4OF(rg`~qq_TqZM`Z(Q}Z`)c@f)X9?z}Abi z)vGM;(giag#hbix&ag{1@R2x~+BW{3@-4kn<D<AHcUGJD3RB1P<^Ztg2-7jDVScqk zW7+MJKlty@q<a!|zzrtFJ<l(BpXe{cbx)iN|LkTNy#_EB_TPAmI7W}M0!1k!jv;X^ zl<$fAOYgK!JDD|Wbou~u3Dy1}xd#lm_WM9=isTJ>Xdh(?EmhZNKQa5%|1F&{TB_^V zkho|N_MKDK@ei@AUp7ZwOqqA*cBVPRanibO-b!4s5BlRqQ;JhiSX_zUpuZ_hqNim& z&WN@u5HQ|3S#*F_bIa(czD^PI>Gb(cpjaMnw)&5)>Xl)Ug7k|<4IcyLuK32olZ|4I z$Xy_(0h|CwGFh48X#QL<dm;kP;WrSsB*W6d==LS>`%wPzGji0K?-7VK-(pp<bp8i9 zQxUYskoR#@87B%Zyj0P}-~>A!I85&!+ndn$HmbI};HCAfv^=8N$QTW|{Mwd3XeZu0 z`Uf@VI>p;xUTbTTm%nCNe2)QE%7?lPgtyF35JxR#uHQ9chQuS)!FsCS#_8%ri-6k_ z`|MNQ;*8sM;Lb)pDd_aRm7|K!=>CO8mZ15LWBjd`IuQE*L8{9Y5_;9I$^ZJ_<N&_* zL7XEPHfy{I`Gm$=gsGqY<yOHc?r%EEc7lKheFl&U|I<bd7jJ9!NbF6rt3@5>uZ_R` z9q}Sd2en{UzD1;#nS{4dk$*2>!}B3z)?;U-+oIfO>I=Q#F}&QRzlJmQT9LMMFMV5| zF#`1;|7}phOgB#;cY6Qv^$7hrVI%{rUkE+tx)=Q2b+<JVe6yW#D_xfaechMu^<-}V zb=SGs59K@klz9?M>5wef-|TUYz@~m}kfC4x@A`@wV(1t8?aeq-6h+Au`Ws)hGS|ok zJuIMKlxC)xJv(+jk$jiz`IoJK(fs-d%w}7Wo>Pdry6rJv#{OR#mAbrTt_S_$nD4gw zMdSKYQB7_K>iqsY%kQs&zYklL_Mc7f{{-f@F#w_D@2&Hn%=zCr;Qxp5`*i$&pN(vw z$`KK~M)NCiGl~fr*?CwOs(u3f<@sWrELr2WCU^XY5to(gW%Q3f<p2c)ni=3K5e*~w v|InWrYVVh2<#+F{UCr3|pa14zlX6;YBH!*pc@Xep51K0%4YYGK?>zYrxazSy literal 0 HcmV?d00001 diff --git a/src/design/cartesian-class-diagram.png b/src/design/cartesian-class-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..e600b0e740fe03b9af4e8105a05767a0c8055f55 GIT binary patch literal 16939 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 P0g}H3yP0*v;P?Lpn-?_p literal 0 HcmV?d00001 diff --git a/src/design/data-class-diagram.png b/src/design/data-class-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..079ccdc9d009f11a2b71190dce1804939a780d40 GIT binary patch literal 13104 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 MV5(nw?)HO!15`hf6aWAK literal 0 HcmV?d00001 diff --git a/src/design/frames-class-diagram.png b/src/design/frames-class-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..f5d2c635e8762623c8a77aef82c4c5f38d24ec9d GIT binary patch literal 25254 zcma%i1y~$Swl+~hun-6Y3(4Sa!7T)LcXu5K65J99F2REg8r<D2_~7mkWN;na|DJq% z_xbleyL-RkVH&EdtE;PyyyrcqgXCq!(4P}LM?ykEmk<|LL_&JxfrRwn=GjA_<%tHj zA8>f!peXhcsd$iR3;5@$p_G^~(mmoQttlrOXhHcTuHk@$go=&$eSnmh{2FLPc9f72 zL7qi^iNVIA#$`T&g!C3kLRe7QWp;PLRb9#Kj{VT~7w!kaPkG!~V|KgKWl2?^6Y{A- zf<Jt~!uy~_Er}&uFoA2A{t;ENNX1U1X!3ckxC$=Evp}gT+@i5g{G1|Nv7*`hXFXAV zWE;1n$9=}-Dc0X%ciK&Eo$bB7z0t6FS5=I5rpITnu~)wG&nABl6c$iD24==(hVw-y zSWCh0<EHi#4dy>>PZ)xMGe}4;xE~-6>DbozL*O(xxYIkENX)YVsy9LMD-C&~DQAna z<RWLE@(IxCsbH#XfNb)|5w_F0?fdrxNxnU-$r^74=}uCD<Q)gJVULiIesju?1$E@T zGlV+CVg;}q4EirFglfDfd->-kbg}$ZdmrxSHVZS^w6mI0Dl1EOTpYu?r!E4y2Ze9n ztEZ$?XW~!oAf|*f+=%h?vC_h6Cc$xmVAu#jM2Uycc?uLW|EQX~DJSRp3((1jeB6=S zG0<3o&o0qRI!;V}aav<p4$CQ$6L_pp|3q0P3NMRLe>bH}e24rxf`-!+UrBOWdrIKa zkCZ=HwJi%5j8my+kmXkimcpKSyAeV^JHD2;T|x#r_@=oojO1Y|n?*ll>tbXLcb)oi z@0c1SxIEFg@f0}kmY$8R+@4V_oYj~lm=^+zkp`wXoeNhZHp?gyRa#AV3=oea2i64X z8KjlnxqPPB?o)l2x1}}ml#AAZxI>(K`AisWtBm9Z1L#AS?n>N>WUd9=`NQVpYP4++ z6!GLg*Xn!h3%^Sn2LAr%W&wXA_6+g;S6k~B{S?^?#xU$^+G;Cpkhm*>(<x8l?Yv_$ zCeTNnb=>h`9<t1(cZXY*oyha|C*i75MTm`0$1Vi<=Nq#voQ4N)T8yZNcAy|3=?ei% zAD0E*vk#k_;MfMwsA<URoC{G^Q)RoFxe@@E>4U}{zjk8hxFt1I?*%94Fw1Xyy$vf< zyp<=wE`keuP9+37y><LL6P0lz0H!Wl?LOM{q&p?Xp|sYvR1Qk{GsOE6-!_yX|54Vm zTq>0kv%NxVbYMe=QS=Lf#sa?=DHesyBPPTBzT}Y>cUOM&?6lLd)3;rp@YrAn)dNjn z3}9jPbzGIn%kmrxl@H$K^DI(3v4ZX`cE<AFKr~WVfgV2|4(y~6^n}g{I)5^*)6tUR zu*lb%@JxaURqz70Lt?_R{2vWK4ET=~{_Bu-Z16VHlcpzF@EzjZM@n_o`Ct&vLwf;W zo~Iv<xe={z9T9c|IEncF$Kk)U{mZF;{PTadAz?+b2DdW2GVV-!fw)ULHXW^v2-fol zbtFhgt!`<q#E91aEFJ$4m4A_#f9?J+&;4Ke`2_ahi|EHcFE^Bb2HX<Zt+-b;8PC4? z%mZ?!&j-j2VkXZZh;QKJ|9AM$ZU5T+uYUh`+doGC&%^$^+auWzQax-?{{G7MBVuYh zZ3eHOW+Az;v9Y<jyK}I!ySciizXwjV%A4yVt3shroO^^N02DHW)8+7OKsz-9gQx<~ z;)4Y|>J@Mu-{o4gaL4i|n}zysxK6+sCLBv{!Itv!a)nG$ySjNFAEe4sgbe_sN7&Bp zs3QnF$Q&9&ZEk=Vm=xH$6LU*T`0?@easWw+_uXX@hwatnW%@V7mZE)2lpW42ETnt; zwsqpqr>`%>1eGVCefbk-@Z2`xUn=~&B{(D`M>+w7^L$}pq4ztklfMBDwUXDPiEUsI z3{);8$H<zZrTWhW>FEaJxeE7p=Pj?a9BWEx#iYSCz!<G|Y1!C_KC~NBXY{*JYV0D! zmB89+_RdYJuoJxZ*U>Ernw0?>{bZ2_5)%_GEiErR__3-gD_z{&pf$&rH$Y={HU$nA z&<D``yqLL3@|Qss6%`&0>GZomBQRWAY*+PXuKt$@oAlI96w8Ob-5))=rl?@`_>#kR zQ5XyHJW@Yeo~mD-&Domu|2W#KSt|E5vPd96QKA4&a#8@sfL2CEW^r-R|29n_i2?<i z646cv7!wim!a-qTKo5P|Kg#@3$A2moXW-eH6CNI33}Po<cV7Yyvhea|vA;eTua}^* zy4uafC5;F;)!H*GA#6t*TzI?&i1^`i#C)b70QUrr|IqDe{()mh`216-?K6=ndC?ET zU@&-pf1e7molIJrF_f|$GV&C-Av!ub3J7J;C;IDhpU%8VM;d}jSDQ!QGOU8VNAD** z^KIW{K^0(5uBQY_#OEqA2$e!Q6Ryfu!n%$*PL+3*iR6UUOgze6DWR!}RVsw0SAr1^ z9f@bvdU&xuGhI^F#9Lh?u5-tr3RwQe#zy(SZs~jNn8?{5xWO*3Maq-URfONpM1+A1 zLtNU$T$nv}@?~%PT*U$`=80G*pWChEk)2>SnND<L#1<p@4c^O-OA?IW6x-`v3b5Hc zrX`D&gd`{bV{|j{mi%pXH9hx!MtQ~J_16hY8=L*nEL13Bxk7z=ml4PRZA995{XV8A zB@zDBsePvEhCJrsri!BH6l4SEN|O->fiPaTB?PXh;-tU*P~#oUe+1uuS!0Y5j4-fh zDuB|ie?|;k$_dCI@By@PXLx>oHY)`A&!8{3+rIu}$-~C$(??wKmqq@k2>39dwSVFy z`Xd2=d_Li`vE15*1=%A;`sa9vf5c#+AS5LDHV4AMk4)d>9nru2@iS;;>bu+9!S+bV z%lm`Zr-Sit-tgi)0r4RHKuw(b5?jq4+0Xzy9Fbo^ft0&D544R_S67FCkR%h3E5K8K zh$z<KBN4C><a&)fR`B4@>}UrE2XnU`BOU~)w2(oES5tFV9-~W;j~SssN19sSUMoKc z_w9Cg^}Le`c@>zwzX7lXfMM%0kFRXtY)B(&6|AkV3l>3GLBwy|c<0!v9V<a#m>;lK zh;j8hJ3B={k!eJsjm4kpSQ5|xA)sO6;zjbI8J`eDrb0M3pVJTk91f!p=jWBx6~t3Q zA6SQbd?xD559O`-0<Dqg`x?ej%&=!DonTBh#PEiGpol5}qdEDJ`n?<}!a4sq#J7%- zgeor;4=yYe70LKzu!cuttI>!69uio8A0fr!)$zXeg{G`m$AGs4o-J0V@6<-Ej{aWG z#4L{RL1-qAU9nC>MRT*lksKbLS?7n0xVY*FeyG@k@{O(Kk<sAw&)xOUb8iBoANa{K zr3i!UoSfMEroI3ga+kW&;BzaAgnGGr?fnXeNabd%uR%)5sN&|V&f#-x?1qByE*B4v z1es!t@1wh7r5iJ3O5YufkCcvyadGT&a?W7_RI#?Nud?*d_#T9^x}R0*@L<w(Qf!=^ z-H$t}qB1;w(&P_%_=!k7Iqpg%7=)zcB^mWa2NqL6AA68>hcZ=7=|dj+`tmszM8H|Y zt9`7!scKKale?gR(Rw!K3&?P&-FIr1MwrV7j@Pi_madzW_CxIBC6At`Jqq!;iEGwy zbp_NZ_9+KDX3-x0GGE$5HpnoFf=bhfwvp1a#Hn;r^8S>1hm8l9;Lgp{C2wz+>$3&5 zfJPs<!BA&sRueI{Y}>s@^w_;qq9P`kW-K9ZH#5)<I>%-+9?8kZUw<Wtlat<<Q!Q2& zwg31T<o5?Xs;=9^iKa(iDI7~{YkO9^tzhjzILD5Y<Jv@QmVwdH*ihsT2Q@YOWjw*d z*>=dW1js^+v^2|dx5kbF^+70#MmSwsABhn{&z>IG?ZA2>)Kfl^3~WK)ttkBTG=<5) zQLt@fe0=H^?Msx$dQYF;qQgwpFFrYA8^$Sl)H*&DUd?|sZiQb3;|-%UTFHC_?8SE{ zyM}^-5RGPE>G@~+9p5CoUy0A`&WtH4vM4I)IXgMUalBANWuVhwK{hdep{9n%Jn8Uo zFJUOOPBkl7p}S4tfdx7Oe4I&(xhGtu;<N|VI~`fuSlk|U>jiEmn5Bp^KU7z7QAyzJ z)K(acaphCt_E|Cc2q?&~5Px#d#DEIj%S`aXG;K|iJfJ$m1bv|?hH~U4mh<MDnLGUD z=@Ad>0f3J5yJcksBygjDoZPYXF}+svlYzbCGiZmrjZHBq@-^(Yxkc1~QxjjF<4K?K z462kEm9ck9M@9hEXbzVA7)l|p*ZgQz0ia;JDIS~(usnx>fg-aZFX9(WL?#cBvc*0D zDqC&Lf*&8^jLPXZQ;|bV$G<o`d$tzs>2|su{*XB)DXBm_Fj4l?TlF;vIl>wC#pk5G zXca_AV0>R*iEU`ma_0L`6bP?cC-f949H}a49TS3x`WORFDKownI>d3>BTy`(dXh#( zMtoLDClv_EXAlL_gRJj#LH=lxn%@gveo{p$)*PI{^ziA+2R`_KDfRKav7WBF$s*lZ ze82jDcteaQTlyiwa54$J_u+KmMs8#V5as9l7gM~@{*Rtt%KSNzI5FsagO;p)mH_)i z@XG|5j>(4&4XL%vYoeh6We%}jfX&j!ZWAfude}0L0I+*!iaCu}`A`!%1@An?$h`83 zl~lk6xkY5#Gr}4Q1YY=%wh}Jl*45Tl)?@<Pp==O_Ulh+RA{w)#8WFm!pcsi7dr**~ zS4AvSrUUyu%R-Cf^9p|WyQ=y*;EWNfbu~tb&v_^wsWbo|=6>_6^#T4AuApWj>ib1J zAO?HD2C-(ew`LHB;_)g=JXAlKN)rTQ{miTT;N17<^}x)G1Sm3t2<gLV-vbf-;*Pvz zo>xVCxjYY;ZXe~sGiB!}WxEePd=xQ7hMydoTYyEl05@bNMy3oC<=c$G`}Go~UE8Q` z{2HFfm#RT!^!et1iXAg&G0H}=ZaMlPVAyB4*3?+y;>lwn6D<R$`$!n}uGPa*CE&M- zq&K4#)l<JBxw!1I6!8In_k#OW+jiRe-QIwc+d=WU`BP$|%B&ZzYl?RQ7McW-WsfZI z^)kv@{K8kM?o-E2>4ym|(YaO5T&2!Y2v0tpC7SR!OR|8opzvD@Fk`M6UPPQJ0;qhX z<ZQ)60k?~db6Cy`-j>kBJ1lf7MC5Eck3)Fr9MzlTw9heUo>-|c*4W9cv5&x8*KaN4 z7^x73e)`kX31VtTJx6q6q3r8w`zZZ7%A^2!fx<1h#xbGS*JJEi^ciEM@mKP0t0^Ta zO=;Q;Y7|0$oWxgR=E`trT5}z4ldSWh!70C8sQb>mHP}2XGqxp7r@@WiYyK-99`Mv{ ziwx7dC1V6r#1o#zJm%iU#~mYGvqyu;b?<F<3zcHsZ*Buto;*Pk^XXk~TjsQ`B1zi# zMl*yVm0ZRg!otF0XoL&la*z-c9X|?I-$R8ie|qiTR)B^?1L&ty65WLRL0R&!g7dLk z_xt9gqjx9#cD`>ggyi~TIYUCEx<c_;P5XaPD^cJ9oQ&#H9>3edm7YAXaDbAS396z6 zqZOuBGr!-S3ro5RzSUz}WOtmUQ^M$5532Cy?2tL8kOdL2no8T*1uf18;f?Dq=Rbc2 z(d;au`WX43`4cfK4gS;vEJrd~F7~>ybs#uUQ)xAs9Un#+rUvjGEsd<Vg1zDNp<oE! zkmwgjN#!W#56YOL7chOvm2n6UxRPRYuE9!4iB;DQ;r!?N5&`4FXF2{oxA&cafss5N z<aO+Orw_s^=zX(a?TpM!jE!?CWkvR-B_xPBoN%Gff%SdBO>eb-<yaN3h8H8fK_Oc{ z=KmYRA>Urkx*z1~q;|ivHI~C+J1B(w6sgqc2MbCel@;!*Sh=8-^LVqb%~V`iY+P%4 z0?&gn!NEZ&Vk?#Y9n_RV5O|Q6yztYba}rJUN*icQZ;o85s$6m)97+EiSa(P&n?i{N zZIBX|GgRp1b7<{+o^~m~egH^k7|ZSS=HOd(VPSn?A%Te@`LyGQc>p`B5^2Vwu2 zaL>UxV_pRAM;Wu6=qbnTJs+2XGwhhykvdj#^3TWXykxRSpI&SNx9RUIkdl^mIZBtg zzfyPsOf)14+uN1GT=JwbBg4MG-VnX*8Sx8xwZ;O~qES@@`UzI~7GtXqQA8}~hbzax z`JfJqu9a88#amROmzHtx80wZUPyC2p2yC!3n$LA$9;8vVVyNpJ*RO=Az>u5ZHAU4h ze~B^fUvPSERjSErwXmF=y|pUH57&^M-2fGVY$$70hwrv*Dl_wPFtaG?(%{EF_l<Q~ z#}B(Wv(1yO``=2MHB?ln3ng=LVp0dRI4yM<=#z#}=ijifuk?)a6T6B`x}Z$DB&aUR z%%#TESk2D_hWYFMbWCPfci+@LnR&$CP~@H&FrrsW!1{fEEyrlV!1pdcis#hpv|?%O zMBXO7FDZP;xVvU2FK1}3=70h4u&Hyhy^u|x>7bVONFHKI<Fmc|ZTNN-s>_$8v%(+r z);SCKd5vi|+n<jg;T3JLMngy4om)@84Z!5YnnJJc2qJ7(L{EwO*Q3A6MHl%GIt5v4 zjhZi9lYvXvq{s~KO@HB4YB1POI!Fee1gYg<J5WyQ9^k-BSUl3XVk8A!FSnl*8D@hx zB)V#!&5<=NKq5ANRtIL8)KTA5(#NEXGvhrx)>S`Zf2t&uBMypYdGGGPhJ_{6k$zt% zSjkE*KhIFLW$V$@QifJkAa7eLKj=zeMHG9`v(8Dwha7W0<vx3%ixgIeZHwj;K0+f7 ze+K3G&Yb#s-FU$SL&BJaKwwt|Utu?&{^OUnAf+Q%i6T$#>Xo5T{L|QN6(K7=nL5Rt zCQIK0p&WU-$QLM`QEb$!<M;J04q1Nb?5KXS3!W_8Y2{<XzE(G{H*JXJ?+dCUu#=3U z2F@^*E|m+sEmkVfk(@|bt_LLqEu8E<qUG(EuWbV(_c@)o?0$V$`uzEBs3|!*rWJLN z1h1b_#%QiW$p}w+VG6H8FPjIZl?X#Z123qA)#ek7lVGNgy#|AVghjz1YOKp1%&yCp zL|K17!ysTTkw3>+Q-dN*x=f#}dI^_%JA(Q2z*h?;MGo~<pMi*$X+%QM^(<L~EDQM* zlpqbCIVz}{QOJzPqYNjxv+@=1`c#g+GUmMdv$C#Br;kp7TE9ai$AL9r5tTWKmW{=P z(r-|;Ot@d(s=+UC7T+*1@ErygvoKmL1LT8nmK0@R$NlC-P0YmIp#Ei<+TGXIq@=>~ z3Th-DSDDv@Da{VHhT}LSDcAw&7o}|vw#ZQogiO}P$3cU#1d_s1OVj38Did@|SzRi- zGs5&~b}(Ps7!x70B=y-r`6T(5(XQ$+IU@f-n6&)>+N%;?|F4r524mOz!XkW0OW8<B zPOG0Z^(3X%o`6FlQhH#Q%qu*=4U9Y9O}#nyGX$|rsZ4Cj8qS7y!MROngQ9O^38wNe z&>(v+sfCaiQ#%w0c!h<%KV7FT%xm9E3TNN2u&#hDVO551L+FCoNCNWcSEEQgu3Nsx zq#xS}60<t6>=JYpiv#;=V;Zr|S5#EyuEpl0zs7B*n`inyCt6OJ72U+Ck?L-8{w=V# zWlse1Z(d>=U}IHo7W8Tl)H8L<`7&{R{L8*>Z1X%CKFzVPk=@`*9Cn~j{)cHZ?KYJa zE>+7EMUHz@@a)-ZEiDIoKkw3zXel02Wo>EnrhLoBP4kAYxe<@e?ia_jX7XOYE%CsM zl}a}ZXNn`F$GM)?%d^wjA0BqS%*3l&ABpYmIrK+rT5v(XjfUd--u2#<cJbwQQ5H0$ zmfLx=WgM{bGqoxrVB*)hj5<SNKZAB;dR<;Zm?;V1XpM*8*Tm9ZU8BxfSw8+K$O2ft z0@9n3WG*QUkJaM*UGMhN#?h@>>7C}Ugzv*A)4~Y4BM7;zr|8rywYljDxnsSX&D(o_ zr=@|jXr3T(r+}<d_^zuAJJoPP7RUyte~94Y;{(omnsM{9#hx)}rLKrFquzlqD6oA- zKdvCZ$!S!cml2guP@B1k%F)uDc?}}lQ@NNtY>=uh6d#L>bZk1HlUjTR-~Jj^R?(rM zt<3`XY-Xb_=>(Ppll}JxDic_<9?am1N8e<@-`T+5(CL-@4sbq9K2DNK7Jd)(U0Hc~ zRyP)^rXqGR+mZDk9lO<9z<|vwJ?-w*ceXZ)mpREAKnmd_<p;_euTD9w(sxa6cO1qs zu`2PgD(-Ie4fXY#&GGSSY7FrOM#z+u#fxj(+w0rQgJVr{@eFSm7~G1VEiN>266rLd zKp+qxQW-xcjN0gOa0WU|F{BI*%5ppX{=^CNaU|*J?5vZ*!(6y_sXf+S&^~Q?tdpGV z*z{PNVXCj703OHK(ip{Qc9|}8AI&-8K$%p7F0kQbqT(v9-w|ozdGzifg4`HBIKPfp zw&Z^F(7$0zidRM^!h|IuocO1rhLTdnzHgw&$|QUGzGq^R*4*4FoyPoOuEIIS%b*iG zhme%iNN|R~m`we7-RGTRVbDa`qjsh+bT^FAMFtBSn~5elt0Z~4mpkq#iCKy|j9;WF ztSsFM8(|}v*`c9mryY4c5NhhY)vm!=z-xoM(eykk^79qdW96x-1d?h#jW=p*vZvzJ z@)=uqL}qz%|K7_lBbM;=tA*-}<GHG}l(-dr2_lTW@341r8`ndyIn%6+B@BSOM0iZ6 zvv0O$)Lr>j1w|)EM;}LKh+OCDJr2KL>B*(?H@P}2=0W*tECohf&U<#}R3=~(R(OXG zIrcP|wWehoSQT9bFDG`^kr_0H%qU{B&a35zjk&HP7GqHza};6TiM0xR2j$6%I0vJ$ zZ6W}#>F9Aq$6vVxpqmVe01pNG6~>{*GC?V7%F4wxgD<VWw%iZ@%J{65EzR$FRq3`e zK9^IWD6gARk*3nGyhic){NzQogj)j-$&dsXORCRaiROuzb*1`?(?3+%7B)A?xm+Sy zc$`K&SiH{)+g?knPHae*fzNwO9=u?b{!^q!I2(-hJPM5uKV|O2>4eW3p-Og*F&DRz z2_$7u2d*$jBk3RhUWNKWEL>qx(1hePqwB327!habzuHo&gWaC(HAuzv3-(Tn?Z-qI zm?dzMRvaYb#F$ccbD)spHQ)Qc-U>I0$EHN%Qc>HupYP+6KxzDaW7l>w`o5Zvhs~)< z**0l%+N&sq4gLA2u7xRW^+DdEvR>V}`eT}JK(I!xS|A35)JT2VayZ{0u)&y^@qwh{ z>HZi;MfnsyGhB%+!6tEvXb-0wOR&@UCag1PHQ;{sZ4`6zuHVH8F%LDk80%es8Ubfh z0iV^bWiN}NlMTgK!%K>!ES5-WEFe+SYKM<ya>CGMYFPb!L-x$7MDFkTl37~F^kZx6 zDxa=cOoGV)d~Q@AgJBG?vyeho=*2ariML_E*p;Fth|yfj<3yNYC<1Fq;u#@`6r=8} zmJ$a$(#kZ~1;#u_Ppc)7Ot4{5KH{7PK%3fEB2+9)q&J}tkd&gnKx1{YoFP~9VZrnS znGw(juO5jhUkT^)CRd9wbALVj`tO`YZexT8meZ=LK8M=#an;c36wVo(#9KZkD>Ot7 zMK$WFBj&6;$zr<unf-#1)pRHw^OSj%Lgpi>2$jc=LyL;IxPA#tclKsMuwEkO=z)BR z9(M|XmZ@{0TS<4-TU|&KiT~vPpsUbBgW2Yl`OPr3>!Xo3<HuM^1V1`W9){<#K)ab( zZ~$2xVHYK;uov<6HgJ~-`#>eW{sip)+3!A<)r>lXQ7%FBeMto!NnECJ<(}y<7163L zKbVk>!V6nY=$6whm9v}z7*HMeO58`pqJX<xxF|{^Dpx~xcXYN_xWhq9PWbZ%`%A2V z3c({a*{3-UshMmoa&#w;BPoDwO*>96;K-;GDa*5GRlDbMc!D}6Cq37+ro+`cy_oEj zoIG99gn{5Uk^H}*Wn%j;LYc5JZR$3S=A|XVUv$3UK!^qs-76NfqSi4!ZsGH3f-}s3 z5wbs=sJ?BT(j4yDDlgrU8mp;{*WeyBAO5{HkhJe=<rVTD>X%S|*pCN7>v1}epD$^k z@-Zvz#(MFW!m+)@jEC;;v+reCVsU3>Q7^|-nG5L?cT#+VJaeS2)TfF6q|GYJW6fVC zcsAtMH@fXT)cu{!M{9~YXW=YL!${TQwh;cooE%7r{HX=FX%*SI?EhJQvWOe}V|IOa z{8lbjATNeB$s+XqpsO)5BYE!c3`7>7P%JC0k=IPH;74AM%=Z4)JL~8ZCujbY=vh!) z94uS;dAMUMS8jj+tT}(18rv!y;LrdbKg{A4naEFA6-r%E(R`*EV|+@BV{Y|+nfxfo z7&KP>?1_XZnfU)0j&TUF#^7sP$ySx1YX3I@ch0|LVU)RzzEJ*clW%BYh~%CA2B_lT z@N2c_ccLY-nB+!}Z$Hk6UwxMC0PF*$P4qe$IYOvam)PEq<|&KPu;&*N+isa!iTpH* z-gZn5tzON2Kepqt0>94RP21DeX8>X_U{HG5=g^9ceh@WZE?%#3)KIuy(LgvsQzNI{ z&7mHlFpGaQ<V9+;5oOi@+ewcoGJweR^LT^FyH0m>piu*w@td!M5tI8}OA(4Lw^YZ$ zY#EF>6x1&7G5y=;B+9QeoyFeL63dJdojfDU*806ooD1>DcbQ*Cu6PwCI3`<^=2uNV z2<i;Ti-)vZcz#m}x+F*T!mD~Y6k)&#pv1u1wb|7ad1>L|gmhEaw<0{{8usa3g_A*H z<4|-MM4b-7BO#R24=<Sni(qn?jyycD?<C4zM7t|%4e|lzxfW7NSo5%<%Ojha(K3lt z0#*Xi%CwlTFvZt8vf1q))&O!DAmIL`$^TZ9qZEmAZu|jr!!9;f3lv|l2~3>7t9tXD zPVHJ5vxFG?+L6&U|6JGaP3q(e?o}XN($gAx$odrNg9t46?(WxFBEc8=@BOH=XWP&2 zY0U-|arIJIbXbKnx@5qv3Ui|DTGCvF;ca{blmTX}uf@BaPFG7cs&O>Nf)PT5q&}Nf z6&)0~QJoUFJqRWPVVL=@lq92tkBC*zeqj-n8?umuKmDUN0onBLdK>Jc%9^-}xLnHg z-2c*t*7DFK>RyT{qLpmY)))z3NJJnHY*_9ESB<OS9EFz)vd9A`qya>k$0YfG*Sakq z?C%jEGXYpbxJn34`{?I`elOBInPXvd$RAgOBqU<GRGiOHqTAquS8Fj8JoC$G77__X zg_h`Y2z(lG<7dsIYpQ{L&90`(+mqYo$=whLJMOPo%H1o4^5`=-C;9xzD$y!ZQ!x%{ z4AYp>*hkm9yZx`oGxjSf`+4XKWB>~SS;Fng=TV?h_^jXjr0V`iMZDCI9=EdAxjK~v z7E7dsmfu%0A<P~JLMmHdRhyr2w#o^_W87v(<EkNfMz>^$tPB#8&fnFnvOHs!PEi;P z)GMtL70hYXg<~FM^ze_muX0mU4W+P#{5(n1C5x7DBDv3I=b^b@6Df{42I4o62@t=@ zL0GcCEi{7Xq15tO6pC18r>b%Uj&K@kd}}O#+ePRAic$NR^S(@I;!3;23KI?qJLlUb zvUk?Gp~;Dw9WxR_LFQY8gkeeKy5u(r_Rhpcp^gH%c0oY+eC?BnNW@v$aTb-7(lEEI zwH{=su3R#sQNFW&h83^vK93z7)WgO)PKWukfqyh@wY0zFwLS(S<%LuV0+2=EaH+p3 zwVkkbc6<!&3JU}I!DE^OQa!(g+E>#&<fw^oD)E9pW?$dy-^M;<S=_Rh=toc^>+oz| zW${QJ6`Q2j2jd%4<3F5LOg+`|Kq{fehILqf%ycW+e~MXLp^gJ+CionmbKakZ(fEFG z+$~&frX%SahE#z&)(-!gL8FuvB{tR&m=9|LGAAxfH2Abrj|-nDGs0R(s09o3#{;D~ zz~dTeDe>o$#_kN0p}d)Y)gYMa1Agj^xaS22_!)vV2R?uFN33(GZUA+b{ivOX>iJ({ z!HTNq8I9K%5=ip|W_FC5@`4Bi0z6O<mov}$V@K)jyxR1;)U9)s#V;_|ZB7hz!jr!i zGjgV;Aiu9*isAPYo7oD(>Qac4*-!Xac)`>Frgmk@kTi|d0XRJ^&wWrfRy@buP<HH~ z&mNcTWUKPsKei+GD;%Eu7x9wUkP}cUGdj0$=^|?xqOKX2q)5&A74#>PKw?rUcuNhU zFOlg1gJEy@-0MzmvV^KWM|_LIeTSV;EkY&Ea^e&0WCq(74Fj*xpx)>14mHb;Y+lnG z<axXhnj26_33pp<Fhq{^9PdMyIWt9MDghzFO7PtHnf9KCt674o?(mkQL)@9DA(_*l zC$FCSenWRqLh|>MjR@5B`0P^yk`|nz34#j9P4!|HUDXqw*g}i+>unt)B~Om9ffE8- z+M$!Yb6v#ZL$cq}iXQMglLRw(XnF5&EPvhdE}u`haVdZ$?Sv`$UK7~q`KNm?S$Wn+ z1f)CTc-;PuHEU}4{JD+iLI29nG+y^Xx~NfKw@7J<%iW8K+1=ccz7Xtg%=;=qG9%6z zl%p*kC&gK9p@>4o>z+G?S$OW^5AzdGbUulNxf!}&LDE6MBqkY_eOn0YTgaQ|2?;d$ zHPjA={O_HwlCUWm7dckmLHn#E1$Yvw3dYl}_h(&857(~EO6)kSr7_Wo!_FW5u=zE8 zcmi?9&dk0zOy)%E3_!;aX9U104QWm`t{o5zgEX(YL|09N)Yj#Ty1Mu@-WMhjUjcd7 zBSWK1{>wFN(bTJ}tMA{_QusU>b$N}OkvDEm#%wR=`Y6Pjj(2^^x+By$I5<LtGj51U z2<!?i+c+M6X&rW#2_{%-xlaWuTi6)Mtt#(#0x;EpG-@MlD-M8_pBKD5mcqDUuq00* zY5(-{m;Vd9-l}fC^dEuRtU?3u4{(alm)=gPoMG<S;Yz}YM9-s_FEfBbm(K?d_jgS- zQM%sh-Yxs^NI}LVO@~kS=j*=6R?&-4*X;XRly@cG#{&XJMrJq1srPi$TqoBDHEJf! zw^uvSXngBcR`<=0TbAbLxv6?2zWbXyJD&AUdguEKm_(eVE=TTKHAHSstrt~RXS^PV z&koR;_{pAZi23=my_eb%0i&1ep6AVxW_O1>KNRqE|7l3VG1WpdX<(Mxgf?6JOjq>W z{5`B0S2C5@Lrne)Ae>B#fswvJKh1_xh}kS-x`_AZ>zM5n$5XE_RaCvYR_pN3sfvoC zjyIZ%ikqokj4w92S^;!JTU(ojMc-4j*N5Iy)zvk46#Vx$tOw0J-NU}DXq4COkVj9^ zez4PD>D^xq4mX@{E=O0m9sT--%BWM1Lrm;>x;+7ub8MubLOAVy&97N7HoZr(eaSSQ zJMtx~o95xeuF1({qGgiZm9^2qXA*hgN?t;Z@YmOCV)t`7O0a7C`to`Gw@Uj~k}ebK z4|fklHjfaJ2-)9XSi&UA{e9#zS7ST%nwwFt7`VaJF4x$iJ7Z+~^6T3V&HRi&0TMX{ zh0RimfN{?YN=nMu`C3z%t|5VY`|a^Jq;E_H#6)Ur7K-zL57*Y-o=L~nKr()rgV<5C z=e6lzGMD)<kg{=tt*--1<zTP8oqD$xpZv0Y1DTVDXBqtwol5cM0i$(E9@euby^U$$ zlJLV8`*k{#BN+7<8*7Zo{5WL85p#dUT}upu^{5$Ok1Fudei}7g*`c|W|IzBqy1J?p za}`0st*tRVi7Z9X)mF~Enx5Xl&Q1Z*<B$;NoS$^Pb1}?zE9%%JN9~yL$vo!^a9AJP z-RW>T-{qWuz&%XR**PU$jDfLauFU8>ODcYTVL>EA)%JivH2F<HyGFy=!t}H=g<Q(+ z^0J^XpWBJJl$2JZ%TXk$7#t3dqtk`NyeH-7FL%czY4r99``_Nd|GpW<5_*qfhE3o3 z2QxiKgc_x|ih0U!Yvq;IH8p9>Ei6bu92{!=0!%id#=Y;_7Mt9cysr1|*JMPaPTNL_ zFR!6cXEcHnwJ{R6yRQk?=W<Ie-uEY4V=8x!FlfThx&tDf>YX4;x7Tjd2Q3ZJCzBaK z$r>;U_DH6`7{DHd^0cS7>k@HUiUS2Y`6>l>*QeXNM8w4OwhnTMNwprAVzuL6g7U;i zBF552cG#z+3wiA*5EUDbF4vQ+M826IW4>u{KH$f)<4ndP)e8z+7L&m^IywS@^ipk( z*FC8)Az-kKZ8O*L24PF`Jv<FdQ2zLdI@ML5UW#U^zM#0cI3OoDa|iQvSomDvzBm=e zrjx?-?wi~5Nr~u{<KtswbON%a(Zk&IBxkiuITl*lQ``0&3NkX@({UvlZ@2XX0d_Y2 z**aFj19GNFJ67@;Z8xELVE{pO+wV*+JVK^<jw)HAb1l(q((2<imL1U91yA+5l7R8O zY7e@**%0`_Z!In&A~J9@-mm9<TclQ^YhhtAR}GG~wFQHdIjJpWS5A6*^xKsRttA~L z6%|K+O577~FAh%+4@@V@g5bwH$Eu3Eqghg@&!5NCu#zS5IG@p$@Cz8N3peKdDeU<z z02KL<@F`Z=E{!;BYy-)Atkc~sLSnvm><orI-|;FJXx|xFDvl;+XN!&npi|$R&G|ax znvifiehu_TbA5N7d2TwW4u{2;w4BXap^-xPuEXxU&sc40-6Y`wx7X)-^^SS)Gk>u# zx!bY^YRa2Io+HNQ7hXpzoy+Y3wCu7;+^{*@5?!W^$63EW00^7504ns5G@NcHWE252 zN|E>WqVn^Pzdgx<!Pvmyk05*Jjc)<S9bF=ikt06YjU4M>5#m+593fw0S~Gc0zrbQf zJDy2ljAPI_-c(Qm_{xJ4&F{%X-7tFxRmKz^j=bW1`1ZVs!0pX_C}Hv4(d@0jtz#ze zbg*XhFmy&Y0Wj3U-Q|#l6(P${@EEjXNIVwmBSJ!2T=whC{J<YEG2bkjwklq1C#k5! z{>t~06ug{i{Fb>68!f+VY~6cj1WbN!>;Yw(ktAuDulsnsvsxV<{;1MeOz#f_RaQ7P zrRS}tF7LKEd~|)j6BO77ySm1NFq5*_8<ukH@P{`yFRs_q++3_OQuiLDaybMAe*AdD zv0mh{op(QeBn{zCFHQ4U>sfZ*pJ#Tfy*k@Vg$5K9+~lRFkIVsRr0&ObDR}OzFGtSO z!nN|Vm2Qddg7?L8w~r70t8OR$OJm!{r4J>4gHY633;W4}mkp&%9C$3sefQH2L`M9b zW$&db*~3rrEjRW&JnlPEjQ$Y>T;QF_ufU@qW9;6y1%wpe7r>QaDN{>7*1*4V305m> zr0gAz12BTP9o7|xQkqlubR5<;*+o`ju8losv!v7{o2L!usj@bEOU}2(3YwdBZ{=jd zWEScZu3}(glvz29K#gJwAi0MvO*fwxmg9BZ&aO@g9~i9^>$QC0c(bYUf5kpjpi%_@ zCw`)fc*EwYV4qQ%yW4lH_S*kl24HPuAp`{2ecqR_!Q|G1ost%*lm>XiL9qj8azcq5 zbii|JYD&wqM&P!0D2C>{o;P0$*j+(>_=j$jo0*Ac>&Ee`uw8WYFkM~Ko*DR7^izzR zf`EXzOU4jBUKdI^Ph4V%{l2x1{_p-wv6_~v?PC{`uNXyN$s*DkAK@Pd8OToRtbZ4# z$UJJ-jm7FLo(18M;k$i+ow|%`^~P#-(N|h#Nz&(~SEbN&D>OI2mE;!%VXk>KaBLbS z0JFD#6b2pTDQ!GIBj)pJR3&?f9hsBkE-WlBZ7nQ6yfi*}u_X`G=p2|?PnTzXpol)* z*{E^t%lGkF#&s*)=kti8m-4!VeNl*x%8`X!^|RjMGHB!Aaf|`iq_?55LF_d&@`g?@ zuhuOS8cED*Y;^jdaI=mkB?Sf6cVu_^b}6uwx6}1KyF8RnvSxaZWIpzOJ7{?)TE99C z>p&5botU^3IJcKoe8|Y@t<BW(J`6l0dm5M>b+%T6u1N_oiI8g&8!PjzUNuK6B(N<I zmRmSSpme5J?M!+)jhgDpPPrE@o}Q#wTwGjFo;2a&;bCFn&u4{*<)khLVVkYzm;z{} zduGOB`IjGzRxxxl&mtGXgYj|%{))P!5kBvK>K@0acQ&aN1k?<dbkY0f4Gxt=Ks);v zAkM#kcXf1_0A>Z7G@5^|p^J{Rxb+TTlg)+_L=@fR8KZj1Qi)qMlZe^eT<umamRr~Z zjnOE1-tl{O%St9$ZAPMNx@z-jlxvfWYBS3oT*X=AwhCthw%sTE@OX#=6;(1M7-}<L zOTGkO33+6=3d)v2-5JY?XG>nby*>?D>gD>B_BBw~6?RFl#Txcwb)hzj%WfsKf#+c0 zqIbdl;_=hFJ(Bwi*RkxOpdM{DNMZ{Z0&&`dZtc&}Guql}?`Fs(v8*hGb`ic(mi}8T z@;u!UH%k_5B@_0#Mm$@F?r|_sEsQ882R$k^J;fx%AS#@0X;~=<QG;n+oq2*ND0nI2 z?r)C0%c7!<Uz;v%h%LGwWL{l~%E_I_;^$FQvqB&|xk297)j;}{9qjhQko=_O?sO=L zqou|5_&UzZ93!B@6s&pvh93|>aoIta!^4p%a)-n0CnkS~tsW!knY4Pqb|&}Pk8+<X zofUXx0E*hmjq)ucIC$(j1S*@%Ikw2~E(54crIaPWBkUvLbEAs45;<0$ueGBk=gQ1p z2VsQ~^RU>CNyeUr(dF0GeLh%Z45Xr_uC9=LOTSn2X2BDeu1B-%COQC<z+RP+y-)i} zpTni?5-Z&ReL!&~uvVIpTCKxGTXGRac~PwSS%V#I>5jN9dm_I~M3Meygf&NRXWAIj zVW6VB%~&%wS~UXMJ2^c^IZAzl{RSU@{sDv(9TX|fVLg2;IJsWUZV-{uY{l=!ND;Tx zwjr-BKRbNrvX-lcjDBTBiQBo$rR3?+v<gIUUbp)Jr|5IlCQke645c*xo<0Cg^~+yk zzzOMgcw)sIXffv}IRleSFj%M7_Od(p!-hg`o1Uqp0I^H!=;;mz05woi`+#tWc8SI1 z&;ibn#OHpcxs0OMeB%k9kM+L476_sOoc@5=0kvig4@;))g60Q^=0F~kE8K;Iq$E#C zQDDjaLcz1W`RnI(&7wEfI{%rk3p3Z9`cXX2k;tzs-u?Sc*lPC-+SAc8om<Y?l!u~t z?*Ne2w!c&&@F^{$H0=Z$OR}bQb92?v1vgp+Jw@9zgKW&t()4S`)>w?`1_@uCjLF%| zCSwutW+)B*%wAs+xH~$ImOHDCid_Bj#hb^=(AjuR3%~~+UWO9Ara*$tCHfJTjwVk+ zpo`5;Q11S`C}`yqJJlVum#JAmloBITfw%J8-~)@7zz+7O70I}qm`~hgzyX}`GAJgn zv8h2+<_!rRA+zBc?c_XZY5w+2V~h&m^5$k{w)SdP)l7dnt-M^*s=;}o@M|}ZnB82W z<eiXiZt)@>ZsMo2{;AcjW(Cc-r2;kQ#$`b*uPA32nUTALlMS6w%NaL-P(O%8_ZnAK z#Xm>w3n$_~y|6>Ys;lKZzWg#~bOBU46*jV+E&GZ|Ee_DrQITf=4$rA?vR_Sit3TES zu-8r^ofdC~=<NpI7trLIrS7RY)W7Am9-Jt+fA&0CL$_=-!u#gY;!PR?acW2m3e7(T zjH=bi?fE?o4IM2FO^d?@$7LLl6PHPS!w$CmVS8>f>3s*_W#ERMpIR0dAT=H$(63J_ zxSQ-1_?1T2Hou1-Naar{$(<Gz-|>Q0ifGlmp1o$xY&7g{{c?Lm{f7`LjcIW9j(45` z5aJG;{`Ts!v*QrhO#sH`Sg%mu!29;bT@!$U=0K{q`WNf;1y`rXJ0}&E;8**Lo`Hct znQYCV{m#hP`C1;|Nc6_Uq}MB=_s2bvV4;8}Ke@+;4mN74u>p@oi8LnhvK}A;^8EGn z$<a)eCr`3+<h;paC4+jVrg+?-e$%h-N|$}DO<sxx3D>(GZzL4Jfy#6b&n5?lB?z-a zMr`uUT}HXTe>X7lI8bD@HCa_)W-)U%WyZkoR%!XmkK>$Vn#pNb+=H7)>A%A~qb;#1 z&S9~+e&KDSQ5B4=W_SbCGnS;1l+qgE$dEy?l25*(ihRq;%K8eFYlG|^9AL+fR!xP& z2{}dw1}6Md`j+nRhSut68TIZ`0s$AHb$z5*kXR?t429iX+N!%U*VonI;(A6!!S7NQ z$fGkAo%W##+-x|hIAYlUIcf9%C2F9F@RRJu(THk|V8?kKEpB56y*O59kO>ILMQbg2 zI27fU?*48w0_X=y5JmtiAn|3)H$cH1KfiAEsrKxwABiz|vD6?!805K~*Gzb<>hE`< zHG!YP<*+qdmFjlBkB^0Ad<)xJTLZABsq>NziFc#bB;f&nzO4r>$_^$^-2Yt@<)?^P ztzp<?Q3GI>riyRLmRR&Zv{;O_8yZ~_*WvucpBUGcmKdzHTg8p<gK*fcUl-syZmk%A zDeI^Y96)Zg)Lj!3ofML>?aLlNZe;-@yCpTnp#J@|wD}#MCj&o!c&5ttHLMutw?_QT z{k`>lL{UA)@4coASTd$nwEQ0WV&}l2eA_#*v>e_^&BEU$>8X>bxm2=4I4UQVV4U0N zR|Ikrt|X_bULQ?pK2rLGSz3E|oQO{}dx{M~3IJUe9<Ru;bv*H#t_=hXLFovkir&)2 zqC3gn_06+(CdNi4{k}=*9S;T|p5$}dtt=?$OR^Q6Q$=CaetodB3cQ;VSx0jKsJxYc zZZP>Qv7qr8B>L;<?%D-K`r5Dar5B_NcDwc~e(!R&w{KG2xhSc>;3W$%{LoA6sy55% z(v98Tk|$@`9xS2J7)#sjyKAmbT4meMW-hgCEPBcJc?lJukPCI(8-C_*dl(2>xVp+{ zC4CQ^UvOPseo$tI)z(I;t!;fV=KT9Z=ZdpEV3?hqH?rtuG%Gh{3Ou<1J10&yT8qCd z$2-!#4!sQ1P~dM~EE&>nbUkjqy7Ipvo?*YOe!%{B6Jz+=m*8OnnCN3c#r&M{AJhFm zmQRYKdrjZT7)t_j;C^^Iwe$)%<_)*w&E2gVXvMzn?oP<8KQJ`;_3qxnM0V=zK$0^6 z{!%#Y`~!h{S(`=p9qSM;xXNs9lH$htzyKua)~DJaA}aitn0|qsoVzMFw=sR$PH>qL zQu`Cz(dTcc3^XW<17?^*-M>2N(E%pMX(e9I1`jumcv{cy_6q$n?#rX=?k*AOB;56l zsqV>Ch&07qdDidSP;>x50}8~&CAIkDl?e>J88EJxB)kONyIJpq^ePvk0H`t@6x~2d zy2$O-$WCW{hNov;RyShWy0~1~923`rI*Uuf7uk1g)Zq+plRbe##eDlHox*F0_%yn% z{U^xf2X6dfO==2=S+O4g#VU-FvN*W0p00P#+nI#N8=Qyol!S~7FBw~`DFqT+uJ_#l z7Y|Sf#8@I?0UnJE3ly3K$H(W`BlADiwHWW)d{YaGXYg>juYbj>b%l9m7V@?@AEaDt z&Vcow4@Nvgw=$1=Db``)CQ%1sSXG3}ros8)BGX}7Uv$LyS*7Bt@)y60PHAVz0cm-k zS6D$EKsZ4=?R;`^q!}qLEu50FkA)@3T!tkm=<Q%{UmlS|jT3BNUQ?`fZsFdrhUx8M zXtW3|Ek#P=JZ3dx&}q7gKm=yt7-hbAE5O?i{5o1MfVlxt8FFZXf}M2^8{O&SI3`O+ z{jIWKAD^-+HcGJz<Oa^VN>+d$7`PewS@ahw>b9cA9GK*k*0LSl?)f5|jhRLH$i3W> z78giYY!achdwf2!G*FmHUs*jp4HI5lTf@OQ(bU!!7B((@CHvYqxWcSiR(9|LcnhOe z8z2Mi5;Sa`_qHu)DN|cK1+d7;q|0IxqE5Ew&QSzUn|pd{nww9+tv_nZ;lzy1oJb@R z*GhF6EiI)@*`jZ8$d12OH*x>EhbC0SbjIgMlO|5Fty8i;@|+$*D*^k2FGlkH^h_jU z_x&=RT-5cbybc}MTgduGsx2;V3{a!IP-`dE4C};0?rE?-+ZyW|OtBVfU(J%5+MUt) z#ncZt;u@O)B6g4O=#cv&E$0ZLJjc(U6QVf>QgrbEY7v{#Ne&W4^TLD6W_L%=qwKT_ z%lBxeQXqa(mI9PA=LX8>sfi5aOhylGv1bWAkOEgdGQpuVO=lHO1cI8<2Z#Wnhmkqp ze`*2#W3bSdbJK~JgWG)DX4}H|$-%+%ys3j+YJFPUWVslEO#X7K(&F9O)cMHR*zI9t zk!@Ft^X72S;o;2n^T}`i;cXk^gXU7=@Zw}+Yg<_>4Bg!+T?F;ma6O6_OhY4kz(~Vm zw<7Hh6m8Z!ohEZyXj{{HpKPWB^<)gGrzI_S)%sqhuCC}<P3(q;lSK)So}N_HnT<?R z%$FU%#mf5GU)mSnJ23&PXtMI~XmVa7`N96#%j+(Hxy-}El=usRqZ5w$!_j@>L~BnL zaIQ-$Jqd}54hxG2>Y16LChPEalXfvSE^TO7kd35@?u8$pmuQzY=jF}JHS8dh&?WMj z<%cnRQL^9cXh)7f#CHIPSw1z@*V{|Y$e1bSoSK{rwrx&0D{VGdY)rVgc?eYE`JmT& z-HHO1*}z~1*gKfkQ%o<}MUx6MIlXJIeZV2cBq!S){o)YD!N+g(YPV0?&#Jc=+YpXP zCNev>oee$Ta6VYBFh1JuJXiyg22`uJmltp6@c;i}k&O2M`P5py(4y!4`-6yq;8rZH zKu^uZ<!MkD46tF-hJEGf0GS|GcWE`b`yyoS`eduhs~rxoi%DLhc+HLWr`rN<CnpdV zwsEY**5mboT_6x;w$qRutVYHpDbj11x5?hIH(oBVRaH>|vMYaO0K*gGrTW&7AJznf zhH;jUuh7bv6G1asy0mXgX7>xc@WOZq%M>o<tTi`e5)M!RFBLj9Nx=)CFmDA{l?m4% zVM*b|MV(BFh=`D|*Ua!&gkV+n@%W9Ben<lx;DCrQ%w$(8G5OVvcM2R|j;s${2bvG= z<~gw{Md<=NdSIht)<HiT>HS;5b2X>Ifq0{CF6(bFUqC6{WJ~}PEVtX<3$&m_oJS<K zdbR6sqg*#&uPFCZ+E3OeoHV-E(u(mF;0(AtwvSeELH2fb>&K6Bx)0lbbsf6cU?^e$ zG850JSL4tv=x+c7H8r2DvOW779nSX;#KM)id3ea1*%s0lr_<AopncAwA_IJmn;n^A zGRPsvynG8st~CmXq~-*wW~a1>O;o5I*1wkDU07&Q@%2`xU}d}kUP&~%yaV1Jd=>ZV zRqRsTags+L0D-gRFq7EX`fJ=d0%(FW#R?X658D<RT7dda;6=2Jh4Ys{dGOw99?W)$ zF;YA;Q)t(AUAak@koiIxrt5V<^yPQo_wN?~ZFPO+w(4seqWMJPji4(s%Q|*@a$!UN zyC7-N)qo}%(qNq`5h^B5JDQS#%tig8`5Um7l|a6734!KLo;}g&;3JyTJm}uVHuy>% z`YavWXMgclXVK&sV14^)PIfl%u-hZTLN1O|Q|((vUEJG{#UfO(MgXp_(WEaQaIkX& z71$wjd+ym}O}ZX-9|pF*mJZM8>0Pl?eG=^~gLq!^e_?zu2UIZq&HQWG-b`K@X|@|` z75<d;)X06g(WZ+$cmn~Jth&|0Q|b?%0Ph*T0E$VaB2-`D#H8@w#wyd0g8&=4NsVo2 zbkXjQY5$!e3HsW9rf&5&9JT{6=<cqcd;8sZnC<Ez1MHd}hts~-r@l{bPi;%M_Cb6= zFf@_8*o`T7v)`~|B-*V)G<Vt0Dz}Q-=KuE@Ct)1cRYL)q0K=9j;|)`mCLQIO69W+h z<Mb93C7JcG?OVpL(NRS~DJep%y1F{}C1A`z-C=|l9AQ2F^<KAK^L4Z>yx!jef3-hi zx7NN45STW2G95?%MA&bO#{GQXH6LD)o4bapr;CF2!>-ct`ghyVa$lS3-zS$8Ge@=t z(q35nsZ$J{C}so-o%Fr_dyIjhMQ3Ptoyf++4;{b@>HrYtYV>JeW=7WxjJ4@xVhM<g zn%yx|dq>_~a9&)vs3`A-hUmE5xPW^uE>?tGGsQ54%BKB*Iw)%TNW4mVem7A3Mt9%= znIC#2R=__?IFuXVKejN9ix0b-brw}$=7evha;NbE1Zv?F!rbbx|C=NXGGlTHgD!-} z8CGQ_0A!FZI5HjV;~aM;Cv;N_4YSF3blIS9-@Iw`?Q{)t7zM~aQqrh1uV!`-=vk*B z_Wwa5IZNVJ)}@614`!N9>gwtii%{O1b3U1`<4%wk6$Z!|Q8Br|u#nd*YfoZ^R=eqG z)e4uI5e2Ax$Nghk)rABwfsXLhgC_TLOcH+B?rsc|JB_aA6Fj^PIp>Y5E2p_eSH1p| zni^}3)2>1)oRCvOHty0Bb&(IP)3b@uq&L1g$=b8{@387j@64JW{Z)9`UK}f?ZbbwJ zYwM39>hK8=b@=?*!PFl$Y%1k<$JDifWNa=R5W0quMTWe5ZasB*O<mowNeVKMv$L*? z%Li+1;ZKLp@bDT_g92%2ECHB)ceP{7z%U4u?&>D;Pwj7zEJS*LptFex$2dCP@#XMg z!3a1$s&`!4UnpncASW-%^&YSNWWXET3H>jL*1+}8Va(#5nTzz?qH~*X0^9BVT}jz3 zi3Dth+sj-NXGO6L9I-x9PUirSpctT-hSOKi52moO;`>g|Nw|$fjn_K#dDMlwwSg>8 z8&60%CNa05u#kUasfpD(DkECywHZBb&B3gSv~W24J6`v`IP&w&{fbY^0a9a=ytR6W zJO|OIhsbmG|GUKhaB1l!epidf8ONf?Q7GNX_;;X0v`qoQfBgDY{^*((NLsvGV)>+h z55$TVW548cvO*1h44rwDIc_D2MI_cc9=Ui=d|g}P<VnJkNQx4o++S!AFdx>t1zrq< zqz!0EFayC?1$La*%~3JH5dv&$#Q&@8x}%zEwmn`8AW{TrQX(LtRF&R80VyIN(h*P; z2^}dRKp=c7V!;R)q!&TygqqL^iWKP`q}NbG4Lv~K@ZINH_q}`HeeeA->#UGDbM~Cs zd$Q;E+j}E8go!QrEU?<F)37~@Z8@kzF2jI_+d1*?4hHWXuNfcyE0PKSfwWobCUQBo zPQ%!1@dnH(Gc0Ui(~)kQDDG!GUbaKRF(|%`S-$C(ewoZ*_a+{=shWYw@BWR?uU`MY zV%w*w0+yWDKG@%X1!P9Umm766^ys`YnRoZQOQZ4o2mlj<pX@5g=0!!Hnf2*p{e&2A zK$4%LVAKWUfv=}gcw7`g?rexHzQPv=D4cui>i~@A_w@8Ud)8k}T%4c(W*JB|xDYX0 zwY3Fr@-jp2jKTn<LE6$OFJD!dupAyPVL#u+m2XgcS4WhdXZ!xA3DR1DJP!8|1(D6c zdLsaCaFs0wgf&@PnD8=lc-UDyj64J2I*!%<$qT0=e9`mrS|W3DmAG6BZ?XeBz3W(! z@TEs(C!>A<|M#kjZ!6#@+&sC*Sc&x9ziMVT@!*kWc?V1!a>Z4TU#o957SR4LPSfZh zPEY>p6JdUt;tL=>-h=HjD#v*-M&fPYYyB--O^0X@O8EL7rC{>g_jiY65%79U@M-Sj zLXRKUf+HY^dti?Ya}&}}h)s_3BhB(ap&75}SfcW^J$~K68>lK@W-HawIAv=bn{u?< zs7jNq8a5$h1CC&S-tYi~p)%P;^+M>UH?4SU6Kk7lpA!<#dXiX+UoB%51wOGyKCE^_ zPNZ%H*x$GCU;WS;=Te}PP;oM|gf`5ZnB{@LY^2W&l3tNC{|>aze0dHhQ~%Gb$V2}H zi`QcGg=K+&V&IR9R$y7B7ua0vJ2WOh(Lxys$|TsMiTA)kd18B7#1N2x53B#4tI)*3 zZX5r#w34)*(92ec6_tIJT=J?LElRe=I2OT;__Gf1=84=ut|^WX`JffgTeb`Af<jze z5SEDIVk7N0l+Bnm;P`siKd}fQtEg@OAg6CY*5AJ(zg1gLCw75I>=ekkqk9^FW)Qgo zRr@<P<D$)?i;507Jwib^)Ro{mh-f?AxdX0uX+5~JAC+EgFxa&Ug~(gLW7U~s-#0KK zOf)Z4I)&bQ^lZx5=CM{a--B}6_(S2v<{d#X-KeERjP(;4>8n4pGwjrV6!J6WND*?B zqzOI&TQY&7TA<WL;wo@{0eAK4)l+~deRAhi+LiZ<ZcE~*{5FK*D!u<v(#c7jx$9S< z07(W@SG1vl^_YfgNV)T7%j?xnm-EP#-MHpRm_slcyuUkCJ@=&v-T1{^!SMfqI_bXk zPvF->^$JHAE2k`0x6S5P2#78a@l7Ac`sDiHJsjpZ+_Z?+-x<Am8nE|5<n9>))|=4G zxFLo_1_ERcAWtXHd>)TEahZ7`gdU;tb7%zY4P8BOKtb3(zEqxLM)6q4sev!`!~D;k ztuxlw*WcTNc<(Hi0D1(VwMgS}IRryg!K#$dX~LkFeMBU0Z>ZhPCTL@Rzr7ZqWE13& zyZQ9|oIjfM-x!P+qPu4=Ml>jtJ%6BeP&A^prkN0SYYED4#P9vW#KD<`SJ@I*lUbF$ zSf#-)$nU<IXNYcWtk*m)O%OFI*-K~{7wWLy+o_?T-vBzVKVZ`aY*|;?%B!lXH7-CP z%ahMnIPJepJ~N#YUl{u`+|0T(*M^{c|DomR=$>M(P#;`4xuCdnMN@BmQr?5(9U~Wa zxXni<8DmX-J+Ud`*AxxxCJNQi#+-lGoyx@6$0@hnF7^ujyFdHG=iavv=JS9z#iMZr z-Pdm;EGnw#x!VhT0*#resn$7a{jkZ`uTYcgBlk2y0HSW6*F21M%m<L|{(_LKYdZ}C z@AJoozPl5KO(`r^;yakH9FZZMViq?uz;V=%WLlkEI0?W<2*!`GhE?(Z2tnNxoGiSH zmKo^JEre#L(Xjx}{?lp=#mvyw!q9LpQL(0}^c<Ip!~SN0qtTCmO1SzSN5;P(Qc`ak z+&%&c04Z-e7&XRCi$jAXhZ+aSsDbDi!g8Sbcz7};<z<-JQo=mTe}=0cQkF*%0mgmL zsn;ZF4Nl*3adyo~wk}93D(Tk}GYP%@?UUJK*MQ5noSbnl%rmV6HU%<IIQKcFjZ&Em z7t!hbasD#X?Rzh@MT`SXLd#q+kV2ew#Ouej`K?DcWa15Oa$Z5-y@?K}fv5!7ue2UV zfX%h8CLr3Y`HEzP_6uLiBfL_rAxB5@TgfbyGzcfNM+V{8i_omP?>7iKD`N5_!{M(T zQH`d-`j+2^-6`%aKWA5wJC-gwl%qyAwhv;$?OfL<<Ot{@c<FMC|0oaP=_kHmwEjMk zC@+|i<e&Q3apK$amo>f<-<e*p)Mhm8&4u~e4UUA0SEX0wdJeSjut$zAUtn=1ia?W% z_8j^;Qv{N-kq51OKPqQ^Mj;qlK5r3DR)iW+^W$OS7SyJ+`Fe95bthV>ig(A6UNf;F zs?j<o{TF*_?#CdLPQo?sZcCrlBP~LaP;;~JK;EeD8hEl=4KnboIJbqd+_vt^nu+UH zbS9A!$w1oSvB9xoXjpu@wX)yFQ~#JL|CxwB_v)=u7JZIRd5PLynGgn^gI~=L33EXi z+1dWYUn1-h;>*LOrH>sYb24~TzuE`Ew$RcB`otS9D~?bGKjdhSZNo3~=DU+?{v($o zXTqB=*u=MbC$QnI9rmSkbns-pQsI@e=yFu2&LRV^((Vkp!#L8+)Kq2fJ2gU$X0#H{ zrMR6i<*_%HgfInL@&!Y>>h*>P19ycjs{I3rjd<^E%pn<)V;*-C?&e2hTO0g%IRUG% zjAa?fja9YZWL-)g+gzPPe(~H3)PfwIukW@;lBe@uGx>Yh$OacU9vZ0AMEuOH&=Qc% zz?0ww*Cb#NA-!bXxC;u70uogP<*5OCFBw(#OKYlG)ZS?DLz7@mhLgYO@W`<UE#8y> z%N4MmX=x-~x5Ghd2ytN`zk>&ECA-p1dYX00CSbj9%fNkcxD;;ak<_hS3QoLc`C?qk zi^CGtSVBU|bT)6`w@X7ZJ;wcS=?UkuKFkt*&kOH4=L>j$d|Tocs3>FSMVeLZKiswo zXqrz=Ekt@+cIisE#>(05dv#PZtCj6eb*bXXnnJCrU(IFbcGuZA7hy5o4kwfMdmD3d z*k=gV<&Hff50xu@Z}%MvCGB$7cYnCAXmp>t-EJzqQ^#}&w{-Y4OsVkE_aZ<e_x1OO zFsbZ!Ay1zc2z^JgiYSLI4;o2&X)feikQlh-d`CQzcNaR+li_1b^`6?RZo`ERwx1*g zafL{&E$hAADmuC=CT8v&Hv$g2)4a|E?b+4;jV4iQ_~?*2$#-f1Sc8bE@#>%vi`x8P zabwI4b?}f7M%7|xGlaHCPTttFt-AP!L4nQym;I^}J?6P&m*}Ij-&gf|hnU4Fy_lo_ zv9rN{OTop0&>=x?=oQp8MO}pX`PEm)>HOKLBdJ6twt#1Zva4>lD-NsEfXEW{kOUPs zE9BAM5Ax;>BixqjIDTn=Y1QgFCBi{!zr2G!K{-Q%-E#v>ZkJTf$`?$_5*n#Fyws0b zIyepAsZO7XuJN6(eWA0Xy7og?ob!u0RSH^T%y$~Iu|WuBGp*hpS}GKLd4}hqaRPk3 z{!5thHU|NHC6vwNuyMydXfAPC8L?e9*^rZ0z5T#HBK~RpJLw0dc;#o0g4m%>65uSK zmR1@tlPR!TMZ&uWl0tYCee_}`jI+Re1QdO$N@pfN5m;M-NK12Hx*TXGry-R2+w3S| z(Tj+UCh?2$n(p|$fmIu$B2PjEQ*Ah3Msz!#(35<`Uf*q1tlOeSGhUwENj&no0MIu3 zyYh<yf$n8Td8>Nm%cHJ}?r}c2$DnGJ8jbDhSRg1YA@QJqSlE~}R0)4bGr9HyF-xEq z5pCv@A-0s4>#HVd;&8ZPX!NKjDR;Dz;xQAUHG(Afx?A}?Z;gCmX0w+JTS6l0&Q~{t zQR2EC;Ao+TKE^e8Gizzr+175?VV@Ba8Uk?ms{o-VeogI~Z=jcmG827x(D?*ZcR0&8 zGds(`<)i80VPkXjOww%VTZ=@Ej7RMzG&xW{_};x|W1~9<-^y`DG6)|F8rzYwfJ(Oz z4*0@eU>MPBp=`Ay@d5rxV;&i@YYT#zSm`c#`-lha_rwE@U}bGNRx6jLIaSr`GIpex zCo1UyLx1SCjSUCMft>|e9z=O=fZUi*K62Tg?^0*gZT2Dh=BEbuU1l!#?@qfT;03Cy zV2d{9PM=1ziDeHJJ~6AWwRjB~Gb#jCbQ((!M-xMNb1lmXoTfwApXjQrzx_f-*LZ;q z1-m&{vbk9S71FQT15}8BkPx&sc<Ljkk^hd0RUx*n%8wD)gXH5n3y_iOh`%`#nqx{C z_;tf9*Aj>9&1{JlPwdHm2st(*9wQ)cb~cVqBr4yk+>LT{k}r6yno*VFJjce0QYI67 z=&??^ig0|;Gz#8E?)%Z@;NAIodam`_UiXB=)hM=X3?@;sQZ)Ek8@y2jb<Vq6CmCb& zK*ni?z$IH%=G`<M9Yp4T+AaE2(rN){c`F-AGpPEgylA6YkWm%yJV53P7Pt27P;~z( z`0B|92^i&|`gvR-u);w1=GGcY-~+Hh=T#mxXBp*Rufv!an_vP-Z8Z!W&qmfJp;u9@ zQjflQI427~sPM+s#lDzwWIg-5y+@o`p}2cBi1K>!hbnPbo)8%nN<ZjLSn*kpt^vU- za9z{fOa{ILkJ)Qpvytbxe?2V3Ms-sXAZle`VJem-4+hc<Xr4-%(u9H<MO_KLQ&7zh zN{TY-+I5Z+Kwo!8OP}py5#tDVI7qvOwpVQrn_11g*T&5EnNc=-jcS(rmI_i+mD2ch zVeh>e)H1U9$QKeWs{JZk_>PJ{xPyD$7Eh7$m}=un^5Y8*PgXq`vyhdS|0734ED?gz zYz(7Vj^p!M&R+6%Dsx_k-vnS=CFuisrDEwQkh~4?YHeIegc%`^o*~q7hRaEZuUJL+ zBYv`)*#OKwSbyHTKi6-zWY}JdKSCt0%6GQanY`IW8_h<V6&tH2Lj*JvV70U-BQ+a) zz94?&to9Q}bIrl&|7iSY1k@mQqphmS7MkGqu3I~KE}0xiGpT6CsWcEl8wZp>kIl!l z*B>@)735r8k#i|0tR*PVY?l-_N2$6}Swq9AvN2OLNAcZ=Y|&To3wOHs$9(HlodtWl zzc-}!3;FZ84kuJ;`Oz7uyV>)}5}s?D8Ck4_1jC@5_ASBe@u&|bZm8(qeAXq|pIpYD z^s3pI6o$3>P1I2c*;`cIp7WW~UWYP1(<(21jhbW3;gu`$Tapf@n-XA=Zq#D}FG?k% z9mRzlUI>w9?8UMu_ojx7%ua;EF!N=7y3-ePvusY{SnTln@QAQ8sM%9~SmaR7V5R(a zzQ<XY0;QGOQ!QMgE(e-0E~`?drbX#>r|H>&vgGQlHvPs9gZ=(-UqYea>wun9WS!!Y zk|WYm4a`zi+79{3s>bII(_N$XNc`h)d4(qi&_jH6gLMchr^If<v{<+<Jt@JmpvNTK zlX6Fd7p`kd&r^v-4~`c-Wz^X?x#;K|t*RkJNqycK%Tq4dyA-cvhw0tb_I9c%YZ@qM z%IAv}mVVV&(%jheF1hf`^tbz0eGaeEHfIo0_(m09{()Z6v>q9@V7EDuQk+&3+En-I zf>Ta!c8QcpD9`7d&n6AMCCvyyj`PF5&(yqRzdai98Oi_JbT==}$az587gKYMCXsVs zOb;_Bu;LW&pD*g_RXSiV3%jbcvQg({ZPY%95^9iftLTN?8vaN>zee&w>vUrjQ4%Z7 zTv)b?RoC6P1Y(J)1g1D%EV}hkQu-OWv^BODSM1{S6(TZ^PJO83O#QAKXK6AzT1Lkr zYEV#HH=I1A<)-8D)RW#u_I!#UL}u^YkZ@Yy&$k+H%|4~Q^k6a2(?tqDC{ACmxAq%x z^z(pXzg|xiUrJv;fqi~FYPo?n-f*mzE#Rhz(hT>u{+LN;?TK91k`rg9OD>OOtYw=H zqNjP=<Yr1+rBwc$m|63_zC0!{{Eq9<n4501nAiOx$AHV7&pXx_)r5-dwXYxC_`Y8( z`wdp&SIN1hgX7lzcDric#q+|g!)EKlBwE#OdQAlx=NOld>ca7grqU~y!y26FFbbJi z$#V@TuCDvT(2o|sEXhzrphPd@AErG$I%i%T&8kEm#0*6?F33o~koi7pkNT`<D^Toz z-EFG)S6yNQ`_7(6wCv6Wd7Y$GoFH_}qX7cwHizVrIzAQ~%Xn@vcVlm2)Jwh)kpJ<= z5a-jYaW6ZwK)qci1?dsoZp!I%=BMV@T5YKRDtdW|9ILHS>EXZ&tY2I4hpVFb)WEyr zZ?ODWZmJ>l4UoPIK)1izaY>2a?KrpOxB-}qipupH>szYb@Eq<F;FIH=WqPVSO`;Pu z2>LhrVI2yg`s{cG<WC;o8h&{9a_Xp@6eGCrYa*~?(RJtB$KP0<w`8DNZi)d}l7NBz zs~x8z{%$~^zr+O-rTSB5;++Onz)zD8;M1R2FEOJo?qGe~Ngz+wWL`ZZRi_lo+rQU% z{)xQ8+~vj{M-6&*w?7+Zh}y;R9}kM55u^Td+wLw9{&)h%?e{E?uYZYpIw1pn&QrY} zpsW58!P+!k4sSHnXd;5fX!tH3t~pZnv&K|~i5X=;uwdf@za>O*9yha^RIs+Fqo(?s zU<av-v=K~i8&*P`R}jL%U)7*tD=(e4-P{&aDu%a`je`6}zo6J<d|AQMK1w?=_HWNe zajAL#h{C+2=7qQayar=bF5eL1Ds|yG7<>W-9CDx=&%DLq`mEF=F89I5OZ9)FqUGq( z36Xy3=_TVbs1VpNC2q~n7YxgR)*r!I+G16ntcQp_sGKSB&T!@Cj$dp!1>UPddR}?N zf+U;jVP|h%xjbJmpTL#IBUn?L-+oF~4^(vJBUe)b1_Lo!a%*(koL9-b6^;^%a62)d zpl~#~RmAz~sC5vlHLH0ioi%_^TbX3xX-zXZ2up*pyuk;2PZaDqTQQqnoz~m?tiv9| zw_V#H`{Kzac)2_V>)5kF(=N;?pQbiWzoB(o%Ui_j`|Pi+cn~wMwu*JrS>G!y?`qTF z#CUsR%{Sg&_p!pSP}nq0sF<WWx)!LY{`R-y{}G^&;(8P6>ZY&%Ciq51kOFh<DpFFG zIkgL{4-+k4&>Hk;59=U<RG{uZ!KA=vG&5c#8mvvctHTG@4tAnA`*Qw+rp&+|2r4-D zTw;alU!5XHuV=5<s=S6_g5Y}?@Vi<~w$YP65L9`3pKieCC+o!?TA=Hk+2GE7*=@Zb zsU~(AxlA5i1bur%@8bCaw?mk79_0g%v_~A48hFY+a(Ig2^onop`UatNw*J07am~ND z_MsQ#P6AE5bmLXRfV}{E8|Y!vMW8|KJNr?E0t=&+iT5btKpE65rE=nn>80dCj;lV} zRi7hHyyudAdXLJX_38D0Hi=`?I5vyF+Hc!AzG}a4&Of=90+#G7nB@uWFUKCv32n&h zffH2Pipq&tF6xbA-QD!O#7L#Udi>}AZf^hM*uOg-U;oiI|D_<mJOA$ZUq(Ha{I}G< qJO1OSpIP1>Or@hEK`yZ4R?wB=O^pQ^uB*q1!8*6~HS*LRz4#AtQ-_cM literal 0 HcmV?d00001 diff --git a/src/design/orbits-class-diagram.png b/src/design/orbits-class-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..93c5f9e0d758d4580ef82397fad7e199703eca8d GIT binary patch literal 23939 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 literal 0 HcmV?d00001 diff --git a/src/design/orekit-packages.png b/src/design/orekit-packages.png new file mode 100644 index 0000000000000000000000000000000000000000..71516cf30b65401b4568941c0ea64e115f5995c1 GIT binary patch literal 29050 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 literal 0 HcmV?d00001 diff --git a/src/design/partial-derivatives-class-diagram.png b/src/design/partial-derivatives-class-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..cc7fd62a147f7118513507e2f961c5207ed61092 GIT binary patch literal 21022 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 literal 0 HcmV?d00001 diff --git a/src/design/propagation-class-diagram.png b/src/design/propagation-class-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..aae78d9145869f528c8950cad908dd52e5f7f0c4 GIT binary patch literal 44744 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 WI>YTxwsXLHkS(6BelF{r5}E*=H-~`$ literal 0 HcmV?d00001 diff --git a/src/design/sequence_diagram_propagation.png b/src/design/sequence_diagram_propagation.png new file mode 100644 index 0000000000000000000000000000000000000000..e6bf168d5a452b0dbef8b37a4d95d1213495c127 GIT binary patch literal 11621 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 literal 0 HcmV?d00001 diff --git a/src/design/time-class-diagram.png b/src/design/time-class-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..cdf7f5226b5b7254ea84c0c0e52659d5cb2bc17e GIT binary patch literal 25568 zcmd?R2T)Vp+b<eL!9r0)k**?LK*3O?D$=Aw1nDXQ5`ln74NXx|5KwxPA~gX)dJRPo zse(Z0BuEzsNK5G46)dm+_xsL0_slsn_s-lI#(}-pdiGjR{XM_6w=cCdm1&MJAAvw1 zG^#4sbs>-g$`Hu@r-$}~EAZ*MbqK^FL-qPqm|OhZFjXPL>h|s~a%p#mzJ1-xt|l$T zT!$}!iywaA)wA2L&h0av)5)v9#&8xc{{g~(<N7(G0PhRTlXsmk?E|TY`yO22RZLqT zrF!lzJ8Kb3G}q^>tz<<LMcPU3tY7k7U5H&Su4b;p`JQb$MIDB{pYXPu85R3}{to^{ z1;7-3-x*d!C-VlpBmXqdpTlbZ8f5AE2L9`EKz@Y^^!MC;(^Tf4qXXW}3F*@Oe1hc- zC^hb=(R9Lk;U978d<#FySMUf<>pwGe^;4O3HS+&FC7l8F92S=STY5NO8W*-3i$x8` z4X}m6@BLY2j4owD*2|MR*)5{~$TInttZ<xbQiL$9`dRio{wBupN9@e(!IB_nE)=)^ zpRMTeC&Kia&ep<CcvxQCJzsHiNWiH@@*kP{lBuz`Z>zk%a!>3Sf)8i?rS|zBO*Qk+ z!cH)syqgx=;To&>iM}>+)N7K}_V?l$(z08%pgNncDg+i~RiTc7zbD4%eyOC;*fJ1b z+MHs$ANrSm2#KeT#a_9FH{cGwf)!`Ln|x^ZJV%3izGsYpcd*FEcmuryf35CcJN~!! z6wzm~=LP<;$s4!|wCOJ;{x43)sza0U_hUu|`BUuaH&&x`@NqFqRrkF4s~uyJXMcN2 zcs_jwvw5m&Jk6*T#QgcoxD<62=qSx!YB_*XI%YH4;CE}^88>1>%?T)2#~;6GN@ZS* z`}{=m{F)GS(GpLr#XNf+pV=ifDySe8aoZpnd1@v0w<dKABhWBT&7(RM^g}mTA0F6@ z8q!VgU8)E`n*XIF9hA~!yuwfi{gBoW>_oZGy1;|<*i7S311JAz_MzYes=)x;%kzS3 z)Z~1X2%77#u7-m_t)Rs)<DWs{H%DQ5%R#rGu3XDIIUTl~D$pe!-EpL#Pg?8!*S~|~ zh9h|gJ{Hd+J7!btxvJAfxJ7i7mv9zo%!^)s{O*RKYI<z*WAD{(-mv?NcAR1GyNWM5 z#GWRUyoA3jb$?>|tXL9G@mnj36FN&M%Dg|%;LkXR9p|w5N2U4qs&XmeoPWE@8SELU zzm)L5XzG7^O0%BEaEKbgzTiGTiretdHuU&YU?;@Ix`$rDER6oT+6Ci`VTIW#pqmPQ zFEX4j!U=u2N>XEz*>V3%kGi-ukC@`aF4usI{z@_FumqHngIBiQyLq7=?`tXHTic<) zVSgqXFnw35<B7FgC{UJs{pMM0D&KGQSl+O4r;3%3Y_tV>gwON18OmyqH}GfXvQ$TS zqG*%TZffkzKe+~$kT_=xLm!V1dsAkv`$u;oX%BZ!eaTFu^t*n4=Gc&!n=5od%}Opc zHuImU;Y+Z;jAQgaQSJnrnZnUw(Zu3^mh`EN`OE*w(EPW({wFK?Kb#o;H=pkA-`jDG z)S4mqk3k)Co;&^Tq$-=;l=Y9DGRro9vaXIzmV!U6v$OMT&-<?7*H2FUiR(`NL|Ly& zn5g6E^<f&m`+wY+-;T|t@6K?!^DH(iYW_Jx;cxRXM4)@#8<(_+4SMdZPvNR74{NCX zGwY^CyhNU7WW#2Zc(TnOA5~04--t%lv+z&-@gmp~fAx0uRztx|3oPMJ704W)kdmv5 z>nH@t{8!!&jS_D7!F^fqpD|BQdbThju6602MWx^QVJ6v9J@~60M^s&FZ~ehtxFsNs z4d2-$4E;$OY66#Q$T$Ach${a~n^omu!QFqB6iOc-_D$oyq`@3akEHN>Q<+bq&Xj|> z@oHjNSSa<Mg;k=nZkDs$N(#LJZ4Uc$`624XC66o@G%7f*>gia8Na@vS_TQ=tw@^fX zHO_0t9uN-n)9AMRZSe{9H}I9nC$O+Nk>AoErmH|b1TtWpZYP+3U(3gkuk|`XJ&hng zfyTr=F!9uae9BL3k_q6(SK~`nx#^l4VGz&nia&^=f1V*veugc&iS|Fl{i6@yEGZ?w zSIQ6p!qwyd_9jp<0CSMn2Aa@;I2~496nDE56kPBnz`a+c8P4t9op}Rax{lrlE_ZWc z$6vxIz;Bt#=%y1WO7NR@2rP_8mj?Wn#E#<DN@WJW$;;?!B&h5O@<@%9egg;BAgd}+ z_y6|<D(D5dhR8Dya2SDwj)0}WFN$IBb1?S>u388a+)qF6sAAZ6hP-ZO6{rK<>}Ru9 zwi_2HAUS9DwoH}hJ@;uSxn2Y^a%69B?s^ra?uAbux@}8@lqtNlgw&j5&6s!zvxvN< zmiSpU{9_IW)%7;h;gGD2=AnVe$$5Ibm|I;(vI8~U9f&*tdIhiH;k<Jjj`Gt(>$G@* zUBTPv<Ga0=Uk)}Xoc5(3kXx%iH^8IT^e7De@aFb|$<7eE8&9e|X)x(4W9B_dc1*K3 zK1)=3$1}!D)jllZ#qyTvRj`-Y7`2dROUE$4lT@HFS>vx~b|d#w2~-``iJCy1B;5!M zj)Xt7*=Eqlhn-lR^}RhuXMf6rag=ay@H#<%vKYDmi$8I1gZFe!>DV#bTSE^HSTz16 zupfQ&!1I}2w<ZD0pJ4L7)ceWS4ZfrUC~mjw?9Scn9)_OF^-&5T-#-niiQT%$Ama6E z1~I(~B_$?Jh{%kdrItUOpne3Zof=DhkWL@H%i?kCn6&I6c9w&Pi9v}qy+zrB?~B4& zW;3Gct0^=*``)f;OU<?<%iQp+yD-3QulppjW#xtcMF>~7Y|M2T3LYGQ)v==5%*d<g zrW>X+uoFe?r4{rGdL2e;WqIBKs76%!L$4tgtKQ;RjT;>gW7rpuQ6^na(Bjr<Z~rdt zVw)sFIEdm_h%oTLL-TUNt0NX1&d%>>AYXCq?dxND>}4i-#AXDY;<O0F_kaqNk`|VZ zIfK2IHUIhXS+`nO%O4JBAGM5F;@O_zOg?CJvEQ~SE%XlHel-7%HpNMHF4(!BBp)^; zt8YGvGx3z{_hz6qZL@?x94%L93p06IN1Rd?e!=>*#}sKtQIxcIxBCR(^E2KiSc3pz zD#Uz+$O<>KBxE4nqu|~q<BHA)!y(z{W|5V-r5d3eov|$t$X(U-d%EuS**gfnALEl1 z$D9p!^c{1lz0S2cB~f2RKlZyNM|@}lR@ARyaa+pNh!S%7#_~OSYU;zf{;iawf}b|2 z?X160Skmg!u-0$nMG>5MoX&l`Oj+DT1de^|Wup4Elxx|b!+0m<CV+3KO$;FM-M5pa zuw(!e!Us2w%kBF#d~<r!X^BGyLsd#EO-wO!er0)EkL?yEL_O&e_BugyKBs<@p-?RG zvB(jy$7VTf=5WZz52<;2O+WgSLHx)cU&cSy_d46Q<4$D+k_-=@x!kBzZCXuO@Io2i zX`}k7duZ3uL9OW%hTUEV#a|5ET(wGNzW-SSl62iA@zls*Is=jZg4=sKdnS*x@kNCV zMtkuDXH}?!wk#^0VNsoD2cy<zUh{&)L8=-RJF~_L-yEt{0ug@W$35N5$LFv$SC8~+ z4TlJ<b!CER`BaTQzGcSKlM`-Y0|uJ?HLzZvQHLw7dg>I1dfdhR194}QNG2N7?>%`G z>P1<K#PRPz_poki6C;&WI}!zfoch+3s!f5FN2^|NgP~^}<kpTNooKI|bP<(m?hP4$ z={fC&R%pb->uEX?;Foby)U{ws2ei1KG4twbJnUy13iJugmC=drpv)WU(Q1aBh`4;9 zA_$qoZ7<&`Tc$5fd;^W#2jOxP;p+zFA@oFI7Rn^YJ4>ru<C@l&19U=uEJy8ys%A?y z=<)TJ-2rBghUK7kY`Q0I37lQ@!p7fqG#^Tkihc!5@H;lg4}5->_Tl<*>H1gYCZR7* zrG227D0LOYr*^P0w*;()QPgGdF*y0tw?KT){mL~>O{uZ>qOx)iYe<c@Emhib3-oBw zQYWTs|4gF+zm$AFgCF}eoe?jaLa#Vc4L=d+sVfq8a+WGNS}Gj)3d5pI%nQ1A2b3*q zXFrJ_o=*<nPKL#Ll`m$ip;a}#G3l?UY+SO?s)lk5*!;WOXMQgt6u@qcHsizg*U(K& zC~nCjl+uTu0ySahPdgQ{H?yWfSmg4p+v}dwEaYO^cne1JLbCIyA-r#7vE9;>$A_*j zIcP>)-h4D>7zGQ9cl-YN#s$!jZK-@Pd=gIY+?z~_`=p>FxVV9O8bc@^9N6rwm@`fJ zThJW~^czW$@M@_T0Y3^$K3$EgF^TG(hfL+3G70>4?^#`qZZRlntc71B>fp~oCwZ{P ztmZyiaBu+&DlTb3Lshx$(WokJdyli&*KpP;PxRT=TLGVC$(1OeG#t*q8=)mU%5eTP zFMw+!XAs*5INHP4p$;RG(nOt6E?|yw-Jskj#!3WztIg6V%kr^?0wEA?qb5NZx49Hh zzLHeFB+gx^a!*GoiChI}B9-|}P_xlFTwhx4OCJ4H@`&p{no{RKpWNsg@jg&;BMg3x z#8;J)N$?{H;Yugv`!j3%U@2brZ|8QbDpcm}5AoVF+@6ywf>cvK>+fbE!oJB%R`AC# z)nv*57zLGTPU4NqGW))?fx%ZIgay;5XA6ZDA+#gELgO(KWT?CI+IN{C+E01HuF)|3 z%!7e@rB(j;&ziw3@d6a<N!mSa6;{Xe{H~BA%snBPUmo;lA^*z~e?`#9ng0(6n@ndu zLqnhwJYTm5I+6~N^Fb!1g<cRb`7!3d*;@16?e1;uh?ZJ`<OBaUiO3CTn$PFx8xd%F z+VGAYoZ{(go4N-^AbVP+No6kO30&2CYxYivQ|8}xvy6p%X53eS!WVxf>ED*%oppkW z^l~!&n(%JBt?<-WS;HIYaWIPB*YLSdznuDyQg#1$;ND(%LFKrP&SU)dT2-J#5S@Q# z7KdY~1~cH^e|#hS6Kc}Hpc>X&gwOR?@0ERVQ9}vEefsvb-0RHt(s}2|?(s7n_D8|X z&6ls4mb6qJbspYmGuu)%jauF%J&cmu*_dHkFBDuOEFE!WA#p=<Rm&7kC~@x-XOI6> z|Ci2rTXfrwE108aUB0%ubGTiy+B%BRh}&%{jneOS#@w5%44El6*~&^56@`b4RE$4h zuWDh%^Q_%a#=Bl^z_>q2Zy=yT->5bOQCVNQ<oZugH0Q&X!*aIMTi0(l&yf=2mcNYm zb7*=`_@N5$aaT4|nhNlV(o@6bIol%ekTck)W}Lfvcrl?gW-iXKS1fd2?*1Ju;g+cM z6@?G(wgdhlm=!bp@sN1Y(b@uknYHOc5_<v>r$w(A+l)-}EKA#vfz|F(x}I|%%zSW- z#C_uMH3W+%#jWXzj|T(~o5dt?epluWT&1Y{kF}=5oa#HiJDY9>MHiOdS*q~L^~qfL z@zU8;NXE4<wW?Qct)>8hmvFQ(Em89@IWd;|z*D)eN<i&n%2wufBVLRj)5zL$l*lyn z-ZQJ)<@`4738%4IL-oH)Lp~pttk3%GKqd8Co@3i;?aZ>@c4s~M1#(++W_#L?iuKBU za9i6}Q+j1?)H`u^9jWS2YEI8y;cph>ghrM5#0W-Y#NOi!i~cXl(LhZ$K1lC%t{5LO zHmleQ7XQL7mb|b^x0Bk!WgQaT=UUHcfV-62X|#@fdF67hP;I2n=5f4!<<;pOKUc;J zj#6H6hc|?l^_qfHIhcxOsq}CEm8ilsQ0dt@52{6#+od-rquSXoefQbTv8qq45^tAS zj_fy++HHp_^Q%HbjROz@F)yANG^fViqEYF9aj~gZ*VPZXIh;E2cPYJ~26Y}5lC-K1 z#Q1B>%6QJ?)ur#c)H``SuZG!JOJW-n^H8#tnx0#1MVQw0uvjUELdg}6^#%cTs0f>? zn8?er#gPl#zmnu$^chCN%W1re!6FrMW4FR`7@aqlyZlX8<6Si*+hJjH7J8D6v|i#c zhk}{J=T!#$rF9*qP4(xSmd|2)m7(fO%^fZcGcN|-=u~;%mPO3_QnG)9z<y@#+Xm>x zIk}OS<vCH|-fjW|-XO5T%(pHrmc_zv(59a_!NXKP(cpIi0!gi1luxE?u7c?;&L$Vk zloNA3!<Tn?H!7F~L3ROaJ^+E(I7n+}KF*iGuAP@sOZj1{#mCf1EnfQ9y@xzl$jNLD z2&9HAja*pryg_UEnSu<K$k-KhsjPH+&5y&eSt<x*Pa+xH{SPKN>CwD?PV_$ofpi3p zJ-$W*^NT@I?$6S1&&|qO-ofJNfpiPU1IA>4*k?Vo_Qnjo`0vXZ*6s7k?Cz<gTw){` zeman|k6geSM1UB)Ror{I-7TjDbKFzVEgyUK``%bX$Q(8+L!6IHzJTue%n{`e<V^%w zdJyMi0g;2yt@Lp}0lH~<hTz*rLC3@#7moKXwjaLo-`s)Wy!D0t^9kG$C5aJ%;>kcG z*S{Sq)}~NI=P9iYwl{x@>j0~K=kRk=x9Uhl(Y={xYL45Ib!tI_A8ZGV1z-Unzt+U| z1R*!3aUV?E&suwm+*-OE^4c^k+Z!c+?Dwc^_&=+t|0T=6+$at63`|t|wFa;pUq6W_ zf@G`;E{~j9ICLtsP%AR@JNF*h5Mx~A;caTlmS`4tP&X)I53!NSg%_54%Kz5&pg<m- zN6$eAuHAl{AVaqtJX+~fR8lXK$}F%aa&D<E3cZ`*$}qLv64w#ViM_Q)S=^3t98=c+ zf(10xgbGxDkBR6@W3_7bF#uc@d6-R&Z`Hscc~R8(-q@Bc%Ch{RR)cp@U{~wZmvK+i zS@#GJ#i_u{qv5)wF@Dw5q~`UQrAWY@N$xQq0XZA>YJ8Jr%rOLkOX-zdh--SmWV*!n z-SVf<SiVn;-t~h9)f3r&-wBL^*Nc--vZ8wZq2kFXV5QCv-?ocNfTC)cV$x%0Ab)Pq zC(cm=_CnhQ)j+Z;XeK{b5a?)+XgOjT1@{CkQpvc+{UdMS*?WYIwn{u4?#=Hz($~sp z`mbBO6SWZ|lf7oHs;h&o=#rHjk~bQ&V1MRsJ-mv=SE6Q$x9qQQ?(sTjcv53uy(Zhu zH~uwT<k}u7^bla~LwlNOJdEN70|jVUaOOxBFzOiN&kKug;ExZ-l^vPR0&9FGYa~b_ zpyd#%;f6FjxMf?fW1<9%GP(%<xXl?n3Pk61`tI}EQv<~D&HwOOVElhs)ISRRH8YPs z1f&88iz7qSaiA|ZY4Atii<DBZmF)U4vZ9q(KUqG7S#Yrfx2idkOeGj0;3p?&uE(g4 z9lUOOq6jmN$s$-a3m!iTmP|ppg2jPg_KgiqW3FvG=&0xb+CLSjkRw0Vjqns%m@5EQ zC2@)_fdyq^c%=R?KEhE?W0ZkG0DH*eJicv2hO*@8HJacwW`dosQ7az=!hP(3@5sc= z(NF`WVV$-P-GaAZLYv?O@_fG~FX-RDM(aT!D*+o85gmeFjCW0$JT#hNdZIxuNM~?o z48>KUK(96Te@bEvq!JAQ2Sy#0w@^w|aiPZ71a&oxlB>%z*ITHYhWTw1&}@^fkwsJx z-@$jm=X4WtRTAOjC0b$d&J|v`TBageRSkiDvmW$pA&M%Ll(4JvJl~SA52CG}?9a4h z5D8~n49ry}c3mb**Kxh~MJLii1&V^5a{RFvqkvZZ5QH9{Y#_^*zjKp=ma8=Xp;z0h z<F3dU*qA`=&X|iRnro8PQWBh!-e?1q0%j}XNy`)E+uh#x1~|)%{ihg!@}&0sOcxbn z_OAH2dCb>|k&t&eQT-8FPT)BDHJh#m@Rp{R6bdlkMM29TRQd)uegm13R)wsI^Nixy z@KB`}rN~GvXvA*A@cW<BKwCzhb}(e^ffXH|=AdzU8Ea*3$8LK6e+)SMjD3DHKR~$T zY5KDZx<U7@=bii3mrn#fb8pY*RaLmL2gI|o_+tbVN{;iKXktkpdLOp8u%dkw8*qeQ zm<seFGq`}Qv{qE<lr`kdyPN!E>n<m@#st_1HfH$dasJU0kHZ1ccz`_2P5)lf+{f!@ zxIhW3jo&X1O*qNKTXFHY3P@ba7c(H*8iay(10j#Uu9k^NQZvwZi5z!6ep9#n#wU(O zh6g;36=A!t;n&fRIf0vGMotyn&GzHz3NCb8zxWjQ@{kqeCQAOtw>&=MXj_)1%}xo% zm!Nk%G|2!#zkyS;@*awggsTra*`pe8VbY&8pbjBK=YvTSp7@@VmAX<z?EzI^Hp)3R z2JhpcX&luu-0+rL2Fw!LD(VZ@U$kRkLI$}CwfraLSjQ3cl_EW2&;?#MFNcCy!El5^ z&v=x!p9M+@$&z%+>Bt$aT29|)=)j<U<dWgn-nZp7-%O{qQiNLY{PPjtsp6yP6rvO1 zo*^3@cGpO!JXScI!!n;9BjqmykCS4SG*bp%gA*&Vf*SSO#@q;<(gM%fO%Go&cDf9w z)tQbAO?msU)gA149mU;0G4o)YLf!$n-jYnZ>6G|V%52;GYRE(RpGtCHVtW=y*IcBs zm3^A|<Ta_&&X&`)dO!5jUeslGa37}a)}@sa=6LSFiM=ie!;9&-&Q!?GdnppMt$-x< zqUEih3Y1Sc;7<#-lYR;N!e7zbKqQ5EV8D~|2)yhK{O<4@wD#F)#V6aZ!8u;XE6Ibe zdHnS>_%86DSA&L@EU^iG8un?vG&NCo41oF=2;l6#d2!9E<x8PAU2Wp8|K4eLU<r<K z72fY<B>$lx`Lo?9?`TEx+T;_jbPfkIS?W7TG~dnlotapHoi}dCm`(cefJ|_h88XiH zj3@-&QjmB;esFEFAF()MF}qQ||JlnA%&3MZ_I0OP`FsI0A73la^yKA-SfYv^fJsdM zaS_%XH@l?#OJzsA4vT+t#yqiBq-+*aKcEXnAOC;i%?^X0oFR$QwxE6kKor%VV6CW1 z9ey_YQ0gz2wO{`DiqWR>cEPZVaZ5c*!bz@6+RuM*?^jXv&U*vDEi}m>e^sR+uCzUX z?DZRbhjh#jM&IT^H-rI?`6o&Olvok;aso<T58O5=d$&B6>IK#P{$s=ufy+2vbtw8? zc(Cp$P(0WLzdiafGKa;*rdx^^oWBI<)AYxa`Lm%nBivQ9e!Y$O^KNNwaku;r4A7}* z-#zy_c8ebFZ~=QCLD79zb033#&lLO#N{Q9>olw-f6DlwVdc>J3Vbw~{(@$Q?-}tG2 z?aM|xIoxf)CF?WH>j%T&@%4XVo%h8nS&H}9ZlYga5gZ%jp3(H`%_tMT1T0(oXN-ff zJ>DRNA6s9Z(h+yh)my1a{;DANe!ibh=sO6Ok5Z3DT|AFHM6c4;CUQd$4Sev@U+KdF zjoK228$f-|<II+-^4WPaKmCzT&Nf(D*u@))+q_y>o}wp*FM4Hlo^L1>zw_zbUl|EY z8!pi>tpR0%a!!0vg*GywD1&cn?n`2B4}eplH?|R6ijLPDvO1OplNag(ulO+xOlG<+ zei|@-64w&{<G+!WsR2#y=e>rI??;y?&tVHpODwK~=um>kyrP5+h7X8@_jN?_iGB z)p+uDbAYekP?zQrsCRqbx14bDtg4lCQG6EPwgk%o(dQpwoGOM-FgJHEqwPebgcq*_ zcmIbp?$p@&_W{m1+&#E^L_SH0>|4LxFtY(9*~iW2u-%opnO!35l@#wMaql!+RB%X# zbH3r@9IL6!(Sec&ec{pqXtqpgJq;8;``Wq33mPW1yU&Z!R_MdS?yC{Wy@4zyPE$?X z`(Ir9m6G=a({sEL(1J?N^<Vz=$nGyTj%JFG(g1@iMHE~TSncHt*G+V+RfZY@6mRV1 z;g2VEGa2b2n!20W(-$e5$^IH@{s%aqCky94r(w(?uc>n%!KvWbnRRkg(ZBrRPTl4M zL_yfCyuAYAkV6SUFW$(wm&AX^s)A5V+*uVu_TK+zz-u@pi8=R?4Q0SZi^l7arGNCL zeo^}=lLClD-(Q6&$Xapz@wwJ$-;;sAs@EBD{Yupc@?~_&qu!dwQy=cWq5tU`<LSY$ zv14Dbr=R>YnySkD;sZnoq&?s;Wq=#`afe^;6(f0j>UL2}X@PVm-qE15C-yahY6&dt z3r>GcK9c^HL9Um;Eh5~UhCiqkmGrCr(r2aQs+rBwZeL`0n%5Adv>B=Y^gJQ#8U1G~ zl_0SEK8Xx~Vf*tILG|Sq`4v7aQ{ga*Gg4x*0wUUq>D%FKtY@nGK=qCaa=L-u_nM6* z@g!5`Ig4|z$y@s1sHqBt<XemtoTIpT?^g;G`@)B!ly|Z91T{S1)z6s!*dWP;g){~S zwFgkc-uw#0|M~M9e#YdTvG`Cp<lg57kZIA4ztW^_yq+$N?_A#+$l)iMeqx8G|6Zi7 zqaMvrikImp#s64)C^=>EVIkaSz@lh#8>z!h_oVJCJp2Er3rl2wF?gR~zK(E4FYdKF z&mi%&0wNTajC=0jyR|as-dDQFDX{xq6N+2KX%&?m^A&YdwEB(CUUQf~LTN&)YA7Il zivg|VYD!M>XXJi%`n%bchu??7J$otSJu1(MYP;OobDYMTtCBG{(BBBwknpjSF~6~& zd=8pIoeK+&_aV)H3p2nX!~`;!ao&_?j{Yshc%oHZa&Nfd$X`-${Fl<>;clBuz88M{ zwIoDDNxVDic}e2F#zTLZ{4#H1;-W4MLXP6~akDpnQ09GYT2ZsT6^eZPM<EcJW`y+e ze;qxoBWaL=;ey21|20;I-{-%(nD{vTUnAou+76DScp2_*3?lNd2HsD+5PmC9`zpEC z+kP~u@#osdQ2&bQ1bAu1WGBJ==6sd4+Yp=mP+@CDleG`>=0hZ6Ox7#-Rsm6Cz2b*` z=R3u&WCId%73(i%XBL@c?kv~+xVn$jt%syz_xvV9qV};FSR>&QC!uDW()$}h&neP+ z(DN3(?_iaYBq=>Ece>+FR505hQhCN_t&R^(l-}8SGm)};t7Iwvv1^#z&Z2q~!F6G! zy3HP2{%M<Ts4%VY+9Wq;4#&vw_-LT;Ot%dQ910PmExe%{J+%lyq-NEdb}hRf#EXxn zI~>Ov+;<baH&9d@L(cweM!PE+a>IB0^@qy|S_Aur6wrCpPWw0_4~(FhM7iTV9||vg z*y+~O@?OOnOnrGfYw3rmHcq`a<x^&<9Vhl)xMD7UOiO~lhX!-HBWE}#h}llqy}V)e z(-*0dRc4=>eVZH(HSOkJtI4tAiyzx_Jsz=KdGx~dz|v~3x!l@_pLK$}5)d;`Xye|% z^x5Oi93rtj(#CqMA;_vN_NJHgGO2oZJ4{P<F(+G7N^ZSLt2<#IMbbf_GMmYFpxC6! z00g%F1|4ku)lK}zj{~fFUJoZDjqMF9T;7weZRPrGKiJ)t!n@*WZp`29NIEL@=sxLt zMxvSLqM~^4mR|NC&Z*r&n0jTw2etK`Iak<>f4TgkP3L*x_;|O`uN<Bd3ZCHUUS);_ z8wZb`f^h^@Zq8Sf47&^>e2}lAR)~EmDZRed377|jlA|duqX*--HMVNGZ&}AXZk#lT z&-KFX1I0W~eFJu)(qviR(UqMfG&(;-yq!|KC@sCudQI~StooSTj~a^2h3b-VsnP?$ zWn6IXJkIl#3p3a(%rLvBLocHlE8_z?KT6Y!_ZnYE2pBXc<}6+t;LC`I%{M-$VWFg9 z9v|*GN<S)B9J4lJUTIR|zC1Z{rwKSpNSV`=nd@+opmFgrxs)DivA|P;@UQRsXP`$5 z+hu)0#f;R6fIr!dD0>+ys3$h)(p*tjcjANTbW_X>u!-JmkDw*4f~(lOuG1n!X6Z*A z-RiqfyIT%B)^T>lJ2P3m7KCx&*C8z8Un<JT3iLCBWzF|mCwjSbs`3=~Cr7M>*lAw* z9__s?6ny0T?$(rCg^SGeq>e$rf+?$Q<=0X|aaK|8KFIes@C#w4;9bUQwaM5_OvE`& znwR)@r^<aCsgL2WJGj1#dt-&fM?P6>wN`~(d`zPLFq>0B)?V-Hvj*Gqt}=SKme?W$ zyGP&E&m2B;KD#8JDA&zz{v5?NT_W?|<<5iE@yG7I%~@P}$=K()9V_KJ|ClOZr_XAl z7g1>6^$=_}xlY7mMbNZ-uEf1%DVD^3(l%W^rZdOXSe75+Kmk0C32-Z`P~jGn6CS*Y zYgOmt_?=uG<B}baO|n+y=ONJ}i#!ct2;7_>o$MhQm)*I7_DasBSf3Z?h!at{gc5uL zi01X(wa;?=Nbk{?Ji)T%$RSh02Zbyx+4a;)s`W0YsFnJ3U!K<oSLklv<T{E5r&Ya8 zG{)25eRj9gHR3LqU%Hv!UwfMz6kbuLF0z%IR|gm&5H4WGrWLmP&NojEDS0voRViUD zuZCn-%(1`))%N?IZFt3$Kbs>3gpg{ZM#8jqhLf%`ruVOJa;PKl-cx54a+V0|=&YQL z<;2}%4-8nu1qb0-iSF)6mHxLf9yBL19Sa@wUbBe95C;Ti)_;6FswFoO9+p+IJ0-VU zhWFkygO0zu6+}81C$&(y+)?-9vab|*MH_7Q(~SJbuv~raZQG3t<_^=jGBGM*#*@WH zkq017)U0mv$^JX}OT-Tk({?XUeNy8t=}*Q+Un-$A9}1D2oc8A^gZAKXNS^^(slpuG z%dPLRGwswru!NEjJ1lxVzL%W_^UW16yhQ(%Gelaq-+N`+*rk7>dS;eIBU*xAe^a2= zYI|c<(C9MK0(YW{#-4SZCpTn(k%IizOk~qM+x<MD-l4LO+ehcccUMBQ`$q|Slq~EC zzATX!-kO->*PfmT-tDd4x#_*QD#Yp_kJfTZklFk;*%l`YGFP(UJB>JfMD_CBY&H+n z$cSHs{=)U0M;bpwDVn{ux)Ldi!M%zF%=uEngIu|RY{Ts+bMJMzn+q~XuXzg@Bs)2% zN=<(}(TEZ<)s%W)vY;F%CR{NZu*?j_&9@c=a}Gj!06EE>qUECL!<#FHJrrxOjbCw1 zhRYom3j8dZ$hEKLSb>#>?Lgroo2v=3F}b>QG@zwLxazxHTR-w{KcafOInix03;LkP zwxF%Y(ba4xpBbMsuGPCHE1Cr3f9p272(tdd!Gzcn47~A^GV5ZN&(;`+NFtJZ@*oT1 zp6rI3{<IdR!Nh%H-K>~NWaTd5D21;yI1$L-B)`r3ocnQRoU$pWlcCkFPFq&LRqaG( zq}sgbB5`zQvfw=A^xaQ`!s>A@)vn*-ZZ5m@TV@O*II_mO;^o#H8WwzfOtSO^8-m%4 z*DazSwnbm63q0ZIdW*cqt51H~`c{Ptp_a;3W@jy>=e_wkr(WQi%OYmr>UlZRg|rCs z>PtSgdhR*<AfWZUP-l`EDxwzc$F)zQK~179PJ9@-fa&syb?RqXPLNoOqM9o<_DAVu z=$rK`2QfR>9?Sr*2u-xW=@WDKe5`6f-WW<&sAv(IWtqMAHGiTV**>U~h$J>;2)oW# zD!b_11N{sWfytLXmAU02BN{X*rymgK{4qS4;fa%T%X8xuQZ7TQuN7#`3h%!mHG2=( zWDGg?ekhefc^Ybz$!^XSsD^Qyg32Q`U+On5I-?<O{uPwW_eMphMXgjOnl<90K5dJ1 zXp@Fr6}-GszHtiad5c&NZ*aXg7MCNe;LIjr71j{&T=Gr<)1CAcppVt=F7FEB;Is9i z;a+^}GVO}Ey^+i8dxIi4b}PHUyBd_LDp^uq=_V={u~kDsH{`k8hC!0xL#25oVP+VQ zA&0RVQ0P5Lm~N^fucC)Jq@m4my%GcS^^sI#;$W^`Y=}pe+pgbtDvnB@Ui-~$!g7D} zXNMm*8O2bDagF#b(m14%<`0B%#+j_GfN*8?aIPMjUtAjt$&P0+EFiWC<VfHHPlft) z=(bg0fi4_@`f<B88Yu|2p$sj$n}X8o;TFM?lRqwV1ZjVpC>gY`xRO%8)`oUBpedV< zeIVRqHxE%chrRW@%$H7W|A!XQb{saf>MO+w`!57`bL95dQ-ZPlk|%WSzP+=)^5}jR z95E|?fiPfK{g|TttL#<C1#ph>6WQE;gV1k#fUhD5<f=C>Ytl*0F@lO-v@~qrx7MR? zyxqEKfTgWoyZa@lVyAbMD=b2MwX5B%$=;2u+Qm;9)kSk<bMyOssX-t_A6@%!9#UaD zP<f%f0y)2q#AJ@Jxm)KghiOSF+1+0A9=<yfw6akJwiEBO)70FqAMxI}dK~P>Mi)Pw z8WwD}(Gz~*m$1q*U!e)Q)0rl;W~e}-<>8uJx$RzapQ0LU5YNr<*|AfK!9>Df6>%el zY4{T;;W?OjpMo|TaoTR2c~UfZOVs6#(B&iE&Q=r5LGSOXLY{Aq)G^_7iilRXNBsck zEL;Riix@V|Hrz}fXdk0I69Thfn!J`WvqJAI>2u+5%{}nEZ0FfYIvHsrq}p75THz>g z0mJG$sDWYct)}cDS&fRhZMyUa9O_ADKn?|uL#Hz0`2g4Ls~sYJCEcd0K}%XvAE}mI zV6&W`5+Yl;Ajxwdp~`IjK7O#$ecSQ#wg<3Jp*W=jJK1NRDd~KF2KwFm-R3((=^BCV z%Zc3&7qWUUG)}2(d{ef+xpwJ3wkO-GKrh23W+$e(AZ%f0D^M~fxP1V@Xy#K_15hF> za_)HGDY>}1SCD6WHcmc{qV%mq2@jD#=+2gtvYwkf>Qq&pa!_WIn%|oIm?1i_uFTO6 z@6Yca)4J!J|DdrWFit$T{oB3RD7#9x$f+oXeFp(*qjuVpbW(Qru4YQZAZ8P%COKq~ zC@aIQ&KfbiW8Lm8(bWY78F<JhL(KZE*A@UElVVpP`QSM4_gtaMAKigMR|FF`nJ-W8 z4<QO=<ax9`5%tDTEw!|_Eo)W-#9x0RB({2lk1$LXfA!DIn%0;T?b5Rngf=%4CJK}U zp;<XbRP;qh7+63d=?bRJIC9F}74KC&4LXMVxXLPn-1+jIwIdGHKNSn81)YYVioB|- zui;bkUvZ(UERj_kGYVzH!-ETz5}x&z14uhQ;lUy4nPw0=IPXp901HK5@K2X$?zQi! z6w$hHSUy)U?WDqUv&t?_yS1lT*~9Kr&ORG@><cF<NVwxAIFAVNg7uY_7BO?5I#S_& zo4q_tZkeW6H-<jb&g5&^FRbzB3s*%Kv<OKBv&f0eN*Pt%g->mhq(@)aCwdca><kZ8 zT1xsLD9-OGj@*1Cnc7!(fBvZ0ei?<h>@-=Qy>Ndl-O?|&{DWZuH}K9YQ?gatUjjj5 z+-*b9#w-t{3{bIt_|r*2ZpfX_s@`#)-k<?|%=ri8=I5i5EdJnS<QGn};->15*&bmg zuh|j(_pxm5<H7}-SI*R2+Dq#{?x;f6jekt)rN2##S?QR@kmfVfZZ7y-^cpH<S+4xt zU%(+|w^cA=*?r04d24LM`b)kV@WK)(2;`ZP6=T5<;=ll+lrS8j`l&&qWuR#Ad)d?i z_Tj=&MwzZ;1!IEW>kt|id?z@tOWup2k-AXG!vX+0cedLHETh<>a--3hOW1~B%tnU- zUAOZB?(q&tOWB9~H}w)KtlMP<k$jp46ab_=tUo?%=585EOomYJIV@Rsujzz4w}>Ux zGcFEOx!`yiciOzpWhf8(Aa{1FWht1=wP$sTW1)(qo8an78m^n2Wf_)O(lWyBFY&I^ z6|VK@8pa@_09Mg%@ug@03AZ8cXpQn_{(&Q-sEmb+%k7KRTT4(3&2(4B=cg}Ima6eZ zcp+O{<qkl<lkOR2!B;X{Wc}GaM#@*!3SSkM99k$HGZG$D_uyS<x290<0X9Cm*I+^j z>C&$4mFrsYvRR;2;uDQ8)&ZdXA+CtJ&af>56^V#iP?YbLJF-J1jWpop@<DbwxO=V} z`{30052&nf(;p4yJGD$-ty4Bh$1r8ufQZhmT=EQ}>2OA)@9sRwa2Y_go%LtE=(RyH zkU`P$gzV+}WiK2)a}<EC%{mUv@7<_jDvmE5xM*HbaPEIM)+-dRJ79c6;Ig-08&6`@ z5c+0;_x2ZZLJ#u$;MkR4vA~Wiwu>|0S@H$Q^+6)T8F0Aq%MF^}n-z?_;4BBGn*hkC zP@KD&?8|loZU90I81HX45SU_j9m;hbv|}Xj*j3s(Enqlt?!8G1di$65a==~?3;~4T z_@#`;L)m}8_?$zxj=u+?8}}*zInN|(H?Tbrm)r}uAz(nJRhswmj(;@8o3V&d54HLd zUs^{i=<Jp6ka^Wl=*NA;^QpYgh^{r6h;*;FI^X@ZGG{?~1kGgzhD#oN7y{Bc*%gmW zZ!K$8EmW}-il+<!3vaA^PsT7MH0jOT_hdH(l$dwm=oLW0Yc&=m3=ke^M|q`JIV2K^ z5b{y_&+4rGu^a&xCp_(pJDND(k^G)$6&}PSnhKIQ_m*LIW1LX$4jal6KuU4%)F`{P z*OyDdOCG-rk_HTaW&0IwbAIsU!ew>ISH^a$q6_8(K|ZO4tuR`-tuqVSW)>`BpceOA zT9?@N&&60TbjqHwj4QbyE=Wi56CaTsCN*zqR-9I5rN#_Xp1bXmOl;_z2QJ*P`LzLC zDecr&6y>=&e2yR(BfI^W&DC}y?ZOG>v1Qz57q?cKF;aA|t@^1F`pwZmNy1<W0l%9K z^6mo!H7gMy6Q0C}tz%lRz&kmK2IgLw7N6hJNjNN6^Mu*mmidVdcunZR*HKkR`AGUg z1cmV12egj?mzvuc`_5v|Dgnrufw+B9Zg0Fl*6UO(R6PJm(WL4w8tC%n&pgTlzMgS0 zIHh<nFg9Sn(fTUO>S|DA6;u(aW?TQgG<IwmfHeJF!z5t9yFa~8^~3pVA;Vc(@7FX3 zV2KY8CwL!D2s=EFCv|lY4wC4H<h>0X7cS3K`?^q2B{)qQeg@bgXot8yH|&aE4HTXP z#aa>Xq<LD32q&~1>nn6;N)&)p{B$kln`pjg7z#mto2OL<8k01(I)lBHjO!Kp`DqfV zwPyHSV^2_LtJgxk%$3&`c?y`J13jAtNVR%e0I?=Lsf~(i{e<T#cM~SJFgu}Xdc=Nf z5`Js)n6RS_LOm0BHSoH(32qNB^ivjtw?2R`X=QWikj9SeT%NhsZFv*LJ%Qn^4n04X z+Fl*)RPz{_=<pHIx*jj&Zbr`{R#{E7>h4Zf7<?9(@D=y!%tFV|(L#2o;IEi9*H0&> zpa`?w?k?B+2=(a~GE39ZG9=rDG~s-j?Av$I$2qhbS?vJ&JKoge;k0ip)5x|-SK}f# zQ#8Cj=v8@VdB*WW7mT*%G^L8xL62NhYnTqA;?kW_UP9ZU)nQWLm4)i5%<3&eq#7Et zwdAKI=afOTy3S`Em$>U$auQLUgt_E<%MXca=qw3t%wQ&HJku%6V2;`5d2z1EJ|g># zV+94v_D-wC=b#E5q-ebO{f>j|gtiw;fH+vBTJ+6Y(u9thkM?YDF1UIbQ2I<UZ{Rz& z1AW$w%9Qc42U!{y_~+jq*|;flhdJsfb@-<`QA^wlq@fi>Q!%PscPo4UnPw2k9;A5? z$i$^<Q87NzFgwt?aJWCB&H$72bc5D@q@DNbJ1epCTGHqU=BH8D9Kic*97S~PH^y^J z^tQV)TawS3?07WwoftCCHf^$H^1ODYQ(nqxr^d8FJZlm|+PO?<`_TJhmI`nm^J<aI z+v~%k)|xBKWm5@+Cb5e*yiw`kHSR5&>4}jD@b&8x8WD`HCj)p#TkCk_fv@`yXK%ZF z1E9PU_bNQWd-97tm1_@<n#aAVw&nIf-0{mUbJ(CbTOW|;xv3~~?Eunr!Fx#&@|2Ow za<Oy=?+lg2j#!!>JF>$KUC9V_eWQRrE-o_DoM}$a`0d!KyaQ47YF!P;z|X)Z2VrNi zXL10u=MJc<>gjh~g(c^MKxiZVUxmmL4G4mcom^4z{VB@5pewUMsCIz7*SG7736|SY zAqXB{&Tm`RV_AEyclzTrENmqH`N)OWL?`Hy>-lL~6s2qx*SBXe+#1X@Y*)HBd(&=H zG{%YVHub!ZGe-_{4H$=ZdXa`5fd4)~JkJ7|Xzd%-Th|@c0b_bNKsi?4O2vHu-qJrz z3gk<CJSkOn*4^^Qq@!?)?HTu8!%B)Em|rY9>bhiSYb>+{+2^rKZ>ud2^lYn(zu&2l z0sv2%^K8`I2L-iUsR85B51`wb1=TJ3l0sogJrkgbKL8F>Gg>0R)^95&6n^854~o?z zP1x#bm7dyP*5&KU1!qkABDi$eY?>86T;1pX8Rdj`GeL@W8iPy(=+iKw)%lAPWk^lA zloFzqiqRtRDCiP*_dwV6R%5!Z47nr>lSsl_Ptp$6ToLGjUfaDE_0tWCxt8)wNipH* zQ-0x>w^PsdK{`g;o@u#GcjzNUS(gEBG%B|3sopgNDD5lmG?Q7e<kk;AK#qf4qRpBb z3p$;P@g?Wqozvjzy9_VfeqTcC?G0DxSD*{=cIXsLa2&H|l_JjMgHy*S8bG;&*S2H; zT}JHp(&Cl+KA!NI9r-lFb%EWVjTo3uK|6#5K0ul)GVp)v0@u?w$KT&1lq}j}R(FH0 zOdP7Y!y;Bygw>Z5-BmNlUIr2A-d=;8`-*D`0(klKi=TxH7y2U<d`qXGz^PGIndmVH z>wG~fzZJ}DrjHswC0rL?_bLj`e>fx5eZFGh`NLyN^g3K@n5E{_-YMD17~mVOIIq09 zW#P%}tqmHj^`n*GIC}uV5ea~$5<NHaiIGX~KzWf|`qBu_5Ae>tyBh$A5CmV&f+mR# zRM8lYD^%jYvh<`$P>y)AR{<PXJZS&k>&3{2ekXPt(CrAOP}v|}UZWj<EbHZ=9xZVL zDLU!_T6^&N7<AqEZu12hIxzsY{mSRMa)vbl4MEXQY4|e{hcx*ZD4?&VbIV*V2OS0I zBhxTkM+D6;6*-0xs&^Xd5&q!@vNs>R1*Zeu?z=Mn)R<dHHCE!sz_D_d!S)t0a30l* zJ1z12r@FX(D49`|{r#mjhNO8@R*CoDoQ52$T}I+u=2q7kos3HCWx<&T06k4dYo-!? zmYQ?%9k|e2lWjh_NR|<H-Js-mRqP36z9CI`gXLO(Q8In&^#WpC33_9|_iYm^6bFoT z@0r2uah2X6(G`_kZy?Q{BF(x!kE%~d902+FrTd+SW8Ao~=Y=XzZhB7{X#h&`p~o&H zfLCdJZ;>k&nG^u6^HO-l9wKL2BI6v=J>*x#jQ%yg{Ru6?awT?yVx%F_cw4{AD$s`G zyX9{|&2mt^{&u>B14Ri3U(gHSyssif^U3VcTS@d_p?gf4Pq#e^P*;naq9NNqQer?p zXkGbE$NI2wpA?~cGN<DCqTzlOps2`LALd$<n$P20i=b(ha)xJMm>lW~ZAjvF1APrz zJ^bloA#$Nj*GvFyEU{XMzY<1xBdQy8^`2})09iF^Phb=`=gX{18Ey6uTBLj@;C<Z+ z#W`iv@Le}IzCDT|ib22^u+i6+1e5`j5nXv+1*(0Q+=~Nbn}*Q%ilc9g`_*!ab7BBL z<JlU07Np`RMjPUAN!PF|T`w*)TnYDR9Wam;pno6;K3-RW&S1F&V{-kig+?rvLV10r zVu@BYE(?{LqfzEn0H61}1ENH3!!43%mAOjZn#+(OIjeyD(H0BMTa^Cx?o<0bh(`e2 zjJJQdPeiB=SGe{&e%816Y^G8C_%urC(!yC%SK?0M0r7DYn$Sz`s;}X`u>c8m0Fq(r zpcZg*BM`ROa_8keyr${_baWr&M>6?Pa$aa5dOW}7i~ZMk-?I%*6p3@w2oJ7Ie=YLb zEUtV{yFZorQ>Q24jE{le0d+IL_MI;V2W&`|B}eS*KyOk-N|89lUGQSVkSks@QmEy7 z7YCUi8;E<8fXuj@d<vzM2dta7O&mO5C7Jm6c`1;gCFAw^jq;8zQuOv)zV_keKtE#M z${~?RfMdE!n~0zr3k4z0jmR%7*@unHjc!JEY6rnmZfyupk8Hw^1(I#)cVK6uHyJ1> zMzU*Oyj6wyV$#Ah#soBmi8s6i@BTF?M3cxa_1*}|0vcOT0PZ8G)#cGT>P@}0M}7qh zS;{TYV)f+WnE;$!4K4?*3CDw;s`XWI(4|m3d+35cLB=vmC-p!X!AR!HY{cPNJAoRF zmFW0C_M5(V17|QUosI#tBG<O}?;upWyS2@^?zgXT`J;NS*|354_H$yS{7ayEz?Df7 z+O+3E7nH71VU84~Uk=_uU{=xGt3mb^1h{x;4PeJ-8lpJQa_I)$HT1bk1>!I)>Hvz8 zHy?BY1LT8<T{ti&pm+v>stT;-vN2pXZjYU+x38E2Vj8qlfDF}XMaIqoWZ6$h{O$S- zCjPW`c3bk}vFtu~ZCfJSY<E?1g5n~*zD+Bii3jyoJfwW7Vu8^HaTt6|u5)2FB;Rt+ zVF4T4ST?n-35=CLl~7HJyZLOhE)<ZYuUO=O1s`@~XoVa;p=^UtI%Qw#csoOrp#MH0 z7sGWZ79?`uvf>CcpRP88*LDGZu#~=T#(Amn*A)4Ka=$lrmlIK53Hn&Dqbv-U`+%;| z*Ro}HlAJ>rF>?GiIHY9V@$**`mT@A+D;J7`FM-Xy8i~TQD=~m15+0l@G3Ox&UX@jW z23T$<Yk`yFJs-Uea%sQhR6;3$T6R8kTKmJ;nYWh{7bY9bZF<Ks+_aeTM52{^gHt<x z5FAj;ZGSiUyu%Q%Z1d?VT4xUQ?(AMG$-lKXT7D;rmenm7gsnU%mi5P#9~on;9)snz z20B&DZ<yo0rGPIBt|t0i`YiQ)QI`Z*QV>O)xs@U|;2;#l2Phl7k*kTyqKrTGg7cUq zXu`$pT)rV8V;+lXjv$`D;;7|E0KNDfn^i%yx;LI(5P|>==BhaL`N2|uSjuS>rKIRy zV4wMn!};ZRX)X}Pqn0IXKnw><&G(uWmwlfU08}D)xtxIo*bMAGdUz*hFOOBKj2&bl zwE3PrvJ`cmHyydambd&xs&wOOljc%>+gn#NkV~~+fC%gHVQ<^Zu~*SoKNa<whJHtQ z-p&q@O@JbH!az(@EHLE`*1qjU+(Kix;21!lfv!(|pWI_Ma5%&3vQ0xFj9F=&Bewq% z^7yk<qZA<P-%2ek_T$X}`!bFI-5sFZJ8-X9ZTa3yOh(sf$q-5gjNkQ?;03SjZiTt_ zB~Jtj2X*11)ya&(z^oA8NJg{-@E)){m6|=tj`Qx-V3RL^4O0NCLjdFsDB=uTfj?xy zVTz8j<=SF2H#stC*#_VUCD}^I#noq<AlDxa%NQEYw=7T-lI+5S>6}!<$36sInm7va zsIBN83;05Tfkk@lnwHDv&vbnTJJR3e3XZDEwiMfJGDuZR&C5mN3W2|ZagpgT?HV}v zrJ^aXS=SQwe9-S?R?Ih-dp28%9782TfC&4Ujyvgi1Y*VPOvZEf&jAiV{1E7M8#(<4 zf!6!dq7vMJ>Iw#{#Tk7=h?X1^uGLZ=Ac+9i>6pz-p%q-CYNTbs`P9aNT(0y*3}Lu~ zZ7u`$KtUhiyK&DwrI)5p%L><e0`9G7ZQ`=_RWx4`U@A#CsgF_#U4Ws;HY-R{4gnu( z0SHMyK9}sTypX57iKuoW3OAD(-Q~&Eu?EIb)g6*G`ye<-7|9aa+F;2KDpP^ln2OJJ z&X7*VJI%k0fQ^~9Q^_Ycsz4Q&L8k}$K1V=#tSK1Rkh^C#5H+;BXPZIIAyjSVkdfuG zb(%D&`rit>kmC0FUbR}7*jKPH{q_X^o4fDcZCw0Pf!jn8Pwg*f1<Yfj#|AB-O<q=Z zb1)0IjALh$jHK|s@b~x+E8rk$8ozsiAlUQ;K$qRckZJ@L-A~TU;@ni^VhYG`&>lTK zhfSRb6vCn!7~LRK>zbze8O)ld<#wyF2c_ow;D8BRFBMH100^oNaEotu**gu)V#a^) z@#TGV8In(pEv&RCvMXM1Z+<iMRheUB?SzG2FNNZW+7cG^TrcX2asHzKqS)zYPWo7O z54RP-n|I=<1&{1lYS=HI)A$@LWe#$%q8nWTDEmiX98EWA$0VLB>|2lAzG_O0k#xGd zE|MXn!wa~9N)UhyL<!kUtwi<{+`&N1xW&c_Re%%a7>1Pb*1g9uh#5id)V)@*vjc;A z_fnY;qC>`MCC7c2NJpHg{r4}ueV3gIHg}~-3jS@|N=L~=E(x&S195kPe-d1j4?h4J z)qER0`?z{-9njpFm`i1g`E3CBPb0q^CPTtKp?%H%j;wXbAJEI5Tv&&hP8m=F-2uIZ z1LXFA@!cd)zjZ(x@ZGqQYhgDA0H6h&49LV}D%^D#YP<bB4S+TUEd;x>W!Q$2>yYDn zVndPy_-YImJWej3bNzH+y&utLTW_xvA)UT;ae+lorG!`$0H_gdzY?5_rzL+PjT-@^ zoX#mBk8}n9VltgKDcBMsyU_ts-Fpnz`mM@)OE{!!<?iFS!jkJzpagF$we$j7?$@J! z??MX}$w3=m{-|}<iQ@tA5zv6&N4(4fUn=b`@_;A~aoguMjok)2r~vSM1ylw7c)F)1 z)pu+hcWN5QCyY}Gqza{=V$58A0XX;yCK4I;p_GD}9YM=RSelvh2PS=UQ`bw9tgK6B z3GU-xFM<DMU<6|&`7*gnN&@Y<vX~9pTRGyI9eBN#)pPA@>+%;ll+GBzo=HXpX%XKG zkVu;6`Cf3wqm#1JdWkh4o8F?*zYg>`Bi)v=g&g{^PPQ}S5A<c<=0BRwen14M;0pPS zBJNGKF#-c8V|Z~+AG7ecn+;<Fao@lpw8#Xf<if=qBuNOEvXMVS2;*M|>X4*!3>{TM zrYdwm%JO@MWw$NiEPe}rrhc_C#Ud8^K*a6GGZyiaD|R*~Jp*-4sX&{<L&l4TiE<^l zjopXT{!hlbEq7Sm-f?5^FNCKlM46($;-D2u>J8+x*4}E_Ql}}INI@U7O^NAzkGB27 z!`mP*cL%2mio_eZQkf$vQ%ZK%LUw<!=Kx?beN)!Cj8JThq@&U_oveGzGc%{FF-&)I z3t@VFy*RvDHu6>pHWdD@L0Q`V3w1C>2%l&Lz?!4EK5Ry<q>-DROW#`<&D`KAN`pjl z>#|Z<@);yN@DT9@!!;aI`99)slEHfSM31r4jEG$5=ai~MN(6n8|GoE}4{iAq;q`=9 z<u}_fG13xE^IT|DXjWrqt>fAV&(+?-YRfNq;$snB8~p-|rjG{5Uw8>01}!9PDHvC2 zeJA*vFMWoP6XUE~p%prBqYtKvK3*uT(b^?T7UhV}^Kht7IqrE3d+^4j{wPO`=-nLl zBMpQoBK4$Mp`KJ+6ZkG%(zmWqPVBYrdN&tBu0^9eMaf}^Pok$}qwjKQ;ssrewnQbi zg#hD|MN^&$CnpTvxD5@Rj_(+5mV<Zh@<!egSHE~Sr%LEEAu4QaL_LF9H=UWl<{Ng) zh!(iamwZ52v&UJzg&$x$EMpWQ8Gn*>63rH0s=&h@)G_;(DL(e7>Q+~5TV#Jm<t^}E zo1LHkDF$!{I@xJy+lBNdY1@yJ#&tL)N*Oq22a&eSLm{OZp}kM%3nmi|4#qi*2)PUE zY}1<~Hb=TmR7Mji;{@3Sn%=x&#dX+qzKUMpc0PWx=<!H#s9Y%HY_X&N$MUG>*PPLh zVH6afjEu4s?$wq%bGb_!E!POR885Qe54M=-%TBJRGMh(zDnXkFvmdKnj5kF!{9omq zdpuO@8ppL85=Mk%kcwQY?J^m;oN96{W5^hX*~}yfGh$?@)QN`B<eE!ilg51(&5ptt zq}d5+NHG*nlwC2R-A!_-^G@aL>SXWzIe(nb`JA)<n$LWmwcd9<@ALkicdhk4-$b5s zCdFPpS^Igi<q|ESzK?#1N`-rXm7(H|2t`b}YOw=~cb%}s<zZIFEICTP^@v<T5mEr_ z^{#w-lDm^Q^jyt6uwnvVzpp$`n-q@n(A}1bs$m>?sz;8TUb07=CDBKyVFPywdND!T zcm2B4l3y6z+9YUAU>-(mJ?m)3@0}Sssf-I=*X#M<K%~~l;qC@Z5~-L~?0czTBLA^9 z=2Vr2>k~@T@al7fx8K86V0-m2s4Hf_Chi}|N_kIi8%++|T@eAD^7863-WfP-rNJj3 z+yT)K_FO1)2LwPSU_WSuUTU&)BP7(O;x#!LzsT<#nPe<!JA)s-Gt`<>#;E$aY_mdw zU%R?}5-F@mqhbKOkE~`&Z+vC{+=LOg_PME~uL*BGq}LNsezZX$)OtJgyd66-)aDdD zE0JLrR!LB#s+6r$S$f~5&BU*JiZfn*qZzO8HziiNt*aA`d8n{qn2q!y7Ey!SdvA8e ztbd#D@v<6uHX}KFB%*0qPN|(s_XXI|v_aCv#MxP>=oin#P0?x2kUW(@i*r_OqA;=m zuw~0_W`i_41$NEb7m{D*HPS9VqbRfg;cGAL>nvMCxIh_}rgP<K|5i@T&;{6-L;XJg zlKdZ5?T;RER;=k|YO3_bmfCF0JpF1sIXg7V*Sf+;Fg3jaY}M*enjrMLlc`pUT|KT3 zCE_(Y1a7ul=#uAPlpuq<+I2=XK@<E@krsJCde_bH*n?%I8ZlcPhe?i`yhU$OOO$zA zX{u5&EbSgi$)4!hht8Z;{K7SY6G3YANYk^<H819NM7&S+uw`&Frt~eT>uAcz8Ae}t zx7T`dBS*Pn+T(#cWE6QT37)>Z_`-%ah#aD_+Da*+NQqxKpo&JW8bmn8b2Da>oBE}z zosr%uxn+z#_kp^h_5u^OG`@}5I_-&V4_6+jJ1hsYsCY%!Ft;GPBvP9SVl9N0Bd>XL zC+>IF9C#gR^~NBw=)3_lSIOAv@fDv5%V34K4=&X9!^V|gtqqFYpoT#)opc3G=+V5} zl>5JJ`(tkIzIi$95h?o8Skz)GW|_+wSz-sPdC}vNY+MtjgfD@jX8P*B*a<?s_i6l) zO;1f;X)nR=47UTXVBRF4DGWpp*$)z%xZ!vVY_T$T1JD2DGt3JTZ1Ku^3!XaTkZR2w zvq4UVBl84w`!Aq~Zug1-a%Ek7zf4R&X41&-Yq#t`nT2AX=~%QJ8BeLrIjjrZgdBGo zc%DASa|j1O#61}UNMYqrn=mqZ=FJ@bVi6~1X|@h+cPczZC61HmG#GIMRIp!p$<m(N z$IJcUQ0>HE(Xk0AtC~F!5PXmr?4=tT)^>*p;AWjwK6zU5)346GvlB6~3N@q>0WIb5 zqKpAk%9R{~@RUp9R3pm3?(Zg{(WUoN??sMa0X6{&FLPJzu8-#gt{8CsHKMC#_pg%a z&!{MXV#%M(d7)p)*%(yK53IW7b))r5zpAse<Jh(42trAA=h^Dgvp3y0D^d;aHb1!G znn!`{RYq-&f=4Mwxy1&laNi5JoUBnC!~mzNVkWtdkxDWl-BPmm4Taf0<H<)G`1=6x zQ%k(Jq&|M5eoLUoYn1abeinJGtwF-CSA0pVrCZ5Hb#ok6mPiXT#up*V%(_?(I5;S6 z&=z)X6E==MvlK)u_grD8PIT~an?RY?=^vgsfZOWgrI)5$NM9b>F1UTN-$18@m*t2` zp?IBlUuM{~Dsfa)2SbKwfQ{Gs&#}u(;$brm^$naxZe}&x2l4c{cSo+7D7Hm|2(GYN z!0FZ#XBAstq*K7!d2D6IIzAv2R<e2b!gzFMr$7%Ar(cI3Hnfr;Sl|*dxwtfnSk(t~ z+=<E{BB6KaDR=1Vt`Q-aqAj)AYTU;{KrZzZ0$_|bK#DT(3T@o5eZ<dVcizW^h_mM4 z;|Bv&<8^$?gdBM@XAQNP7UF8!1)zrxpgIkR>V~n;C+Me}*pWiXDLl3o->%JS_P=r~ zS&B5Og@a$d^i}@=Baa!tPLg;6`j_qk+^j%tAFQ2<uzHq@Puss2of<aQ#{I_NfKuYH zHURV$ZDOvNR10`wZ|?dsB>PylUJ$gsqiq>o_}+G|mC~mnd2b7aUkFLd%EYi83Fe_H zsQx>*TT4NkwgVAnX0jcj5Kn+tnEi>5T~19Smp}%v!NfBm>3qcia#po$JbD(rDUyEU z;sX6<H5WN5=?Y+j+q7xe;BCx9h%kl0qs=hDitFk%8Ql7)Uyi$P1dc^RMxI3LyOVR> zlm#3n8g*8FA=uGdp)WmQWc-&Cq@+IWiLEX{2KVd$03ueQWo1DR&inUI&lU!RU{JBU zC*d)mL@ZWQu(EX__d#e@^C4r6fnxn#%ozsPuHo$U_A|8?CP&L`AtN<bGrC6<-=>C& zTywB)JxvK2$u29{4jo{**Z|P6f%HMu*P03+XOXQsBb6d!2N|V=!8_Rh^mXBHv6p^{ zWgGZ2W12c`od03FXh9OG5OEDu*ZM3Jh>yG=mZLOs8yt%`Kqb-gqHgl)%B0i1wjJn~ zs~_upC$s@R4Zbe*E<kUrCu6~eU0BWf6241mDHItU5;th${&{0Kx3LkW`PPA6<UmT1 zu<t~<*=Ytf=BO?4GY2HH+c9`exd3v7>`NW@hK>(sO<uF7C1Ef8XhHkhA%%#*9J8sL z-+f%!lz*<6H}wUk{FLoIpw0_d0+xDUE0}2Y^M%c@=SRg61vU7<sc@QdKKFOxetjUc z2#LFDr<vgWwJH?&dYB_r!mu;W^rbDTY_=4*m#ei1@KI>-c)5q$w?_2zYJus_8SBz) zEDoMvweW-<@&rl)Dq5`pQO@tHc9ZaFR=u9;aFG~^OhA!Uium}l6Hiy6d~t$#VdW-d z2afvtbbsk+I^FyG?|ar%di}vQ|3tI@(<aRAZ{F1SYUSx&Our$02fb9J>P_=cA|SAK zl^0z%w_X88xo_Eqb~BTzPm8p9g%v3kC>37&4G{*<lm{}`M3<}oj?KR&612tqOq$o$ z&*nS655L%_@OZNA%IjYx5~6K;b*M2?m_1qWokG%PPoff#U^)$DDEEz-AzE*U!Uf1q z>Lg<q>c>LF%D9DNnOFM<2@I?w=5yRbfDe=YQ~BYG=cj)wZ+_P7LaFut#-v6==g}td zGsy7m2;J>KG!i*;At7oxqxi)v35ioxm)+x9qr%4QXZ93Tv=pO}vIaYF*oPl8^(Spq zACuBG1us!b2Ul~Z(|3q65-2+sNu0Zz*scdcm$jleF2bVFwo+66C!9b1>-{O;3*S?3 zB{{1~9$zi_?!EkUiItyhj=Sy2dn-`gOJrB5A3HMjQ*1OIPMeS#YMJ$T67y5(ZjEWm zRAw9w<ylC{q`8UBBB>Xp$%oEB57xR~%c=YBxSZ=X8z|Q7khs_yC^r7Xxnh=hxH1dz zctA<)L@7cahvL_65R-JCzl2;qg|kCo_=GLC+FB5N8rsuAT!cRPUwXk`1Q31m+g|x= z7W~Nbi;nATA+bd0BQsY(sktorc^)eQ69qzm_~P$^0+f*-McA7@JOGY#76TT}ag+ay z>8-zl7OxT_-4e4z2f-g|=8KL#7nHB)=s5=&Z34%oN&H8PsuIcvgYpKdzF{UBec9d( Q+=$pt8%JxlrB~EH0DFQ06#xJL literal 0 HcmV?d00001 diff --git a/src/design/tle-class-diagram.png b/src/design/tle-class-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..5cac94d6654d0472d45015c2f07c40d9bf1e1b21 GIT binary patch literal 7119 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 X=6vd<ts1~X9q8zxV>T5B&s_Zv99sqd literal 0 HcmV?d00001 diff --git a/src/main/java/org/orekit/attitudes/LofOffset.java b/src/main/java/org/orekit/attitudes/LofOffset.java index f7a511a11b..03ca560ea4 100644 --- a/src/main/java/org/orekit/attitudes/LofOffset.java +++ b/src/main/java/org/orekit/attitudes/LofOffset.java @@ -75,9 +75,9 @@ public class LofOffset implements AttitudeLaw { * // note the call to revert in the following statement * double[] angles = offsetProper.revert().getAngles(order); * - * System.out.println(alpha1 + " == " + angles[0]); - * System.out.println(alpha2 + " == " + angles[1]); - * System.out.println(alpha3 + " == " + angles[2]); + * System.out.println(alpha1 + " == " + angles[0]); + * System.out.println(alpha2 + " == " + angles[1]); + * System.out.println(alpha3 + " == " + angles[2]); * </pre> * @param order order of rotations to use for (alpha1, alpha2, alpha3) composition * @param alpha1 angle of the first elementary rotation diff --git a/src/main/java/org/orekit/bodies/AbstractCelestialBody.java b/src/main/java/org/orekit/bodies/AbstractCelestialBody.java index b1f1936617..6b31d11bc3 100644 --- a/src/main/java/org/orekit/bodies/AbstractCelestialBody.java +++ b/src/main/java/org/orekit/bodies/AbstractCelestialBody.java @@ -44,6 +44,9 @@ public abstract class AbstractCelestialBody implements CelestialBody { /** Serializable UID. */ private static final long serialVersionUID = 6769512376971866660L; + /** Name of the body. */ + private final String name; + /** Attraction coefficient of the body (m<sup>3</sup>/s<sup>2</sup>). */ private final double gm; @@ -60,15 +63,17 @@ public abstract class AbstractCelestialBody implements CelestialBody { private final Frame bodyFrame; /** Build an instance and the underlying frame. + * @param name name of the body * @param gm attraction coefficient (in m<sup>3</sup>/s<sup>2</sup>) * @param iauPole IAU pole implementation * @param definingFrame frame in which celestial body coordinates are defined * @param inertialFrameName name to use for inertially oriented body centered frame * @param bodyFrameName name to use for body oriented body centered frame */ - protected AbstractCelestialBody(final double gm, final IAUPole iauPole, - final Frame definingFrame, + protected AbstractCelestialBody(final String name, final double gm, + final IAUPole iauPole, final Frame definingFrame, final String inertialFrameName, String bodyFrameName) { + this.name = name; this.gm = gm; this.iauPole = iauPole; this.definingFrame = definingFrame; @@ -76,6 +81,11 @@ public abstract class AbstractCelestialBody implements CelestialBody { this.bodyFrame = new BodyOrientedFrame(bodyFrameName); } + /** {@inheritDoc} */ + public String getName() { + return name; + } + /** {@inheritDoc} */ public double getGM() { return gm; diff --git a/src/main/java/org/orekit/bodies/CelestialBody.java b/src/main/java/org/orekit/bodies/CelestialBody.java index 48cc229c85..a06fade659 100644 --- a/src/main/java/org/orekit/bodies/CelestialBody.java +++ b/src/main/java/org/orekit/bodies/CelestialBody.java @@ -57,6 +57,11 @@ public interface CelestialBody extends Serializable, PVCoordinatesProvider { */ Frame getBodyOrientedFrame() throws OrekitException; + /** Get the name of the body. + * @return name of the body + */ + String getName(); + /** Get the attraction coefficient of the body. * @return attraction coefficient of the body (m<sup>3</sup>/s<sup>2</sup>) */ diff --git a/src/main/java/org/orekit/bodies/JPLEphemeridesLoader.java b/src/main/java/org/orekit/bodies/JPLEphemeridesLoader.java index 6a181ae903..6c48873dd2 100644 --- a/src/main/java/org/orekit/bodies/JPLEphemeridesLoader.java +++ b/src/main/java/org/orekit/bodies/JPLEphemeridesLoader.java @@ -207,7 +207,7 @@ public class JPLEphemeridesLoader implements CelestialBodyLoader { switch (generateType) { case SOLAR_SYSTEM_BARYCENTER : - return new JPLCelestialBody(supportedNames, gm, IAUPoleFactory.getIAUPole(generateType), + return new JPLCelestialBody(supportedNames, name, gm, IAUPoleFactory.getIAUPole(generateType), CelestialBodyFactory.getEarthMoonBarycenter().getInertiallyOrientedFrame(), inertialFrameName, bodyFrameName) { @@ -226,7 +226,7 @@ public class JPLEphemeridesLoader implements CelestialBodyLoader { }; case EARTH_MOON : final double scale = 1.0 / (1.0 + getLoadedEarthMoonMassRatio()); - return new JPLCelestialBody(supportedNames, gm, IAUPoleFactory.getIAUPole(generateType), + return new JPLCelestialBody(supportedNames, name, gm, IAUPoleFactory.getIAUPole(generateType), FramesFactory.getEME2000(), inertialFrameName, bodyFrameName) { /** Serializable UID. */ @@ -272,6 +272,11 @@ public class JPLEphemeridesLoader implements CelestialBodyLoader { return FramesFactory.getITRF2005(); } + /** {@inheritDoc} */ + public String getName() { + return name; + } + /** {@inheritDoc} */ public double getGM() { return gm; @@ -279,10 +284,10 @@ public class JPLEphemeridesLoader implements CelestialBodyLoader { }; case MOON : - return new JPLCelestialBody(supportedNames, gm, IAUPoleFactory.getIAUPole(generateType), + return new JPLCelestialBody(supportedNames, name, gm, IAUPoleFactory.getIAUPole(generateType), FramesFactory.getEME2000(), inertialFrameName, bodyFrameName); default : - return new JPLCelestialBody(supportedNames, gm, IAUPoleFactory.getIAUPole(generateType), + return new JPLCelestialBody(supportedNames, name, gm, IAUPoleFactory.getIAUPole(generateType), CelestialBodyFactory.getSolarSystemBarycenter().getInertiallyOrientedFrame(), inertialFrameName, bodyFrameName); } @@ -899,16 +904,17 @@ public class JPLEphemeridesLoader implements CelestialBodyLoader { /** Private constructor for the singletons. * @param supportedNames regular expression for supported files names (may be null) + * @param name name of the body * @param gm attraction coefficient (in m<sup>3</sup>/s<sup>2</sup>) * @param iauPole IAU pole implementation * @param definingFrame frame in which ephemeris are defined * @param inertialFrameName name to use for inertially oriented body centered frame * @param bodyFrameName name to use for body oriented body centered frame */ - private JPLCelestialBody(final String supportedNames, final double gm, + private JPLCelestialBody(final String supportedNames, final String name, final double gm, final IAUPole iauPole, final Frame definingFrame, final String inertialFrameName, String bodyFrameName) { - super(gm, iauPole, definingFrame, inertialFrameName, bodyFrameName); + super(name, gm, iauPole, definingFrame, inertialFrameName, bodyFrameName); this.model = null; this.definingFrame = definingFrame; } diff --git a/src/main/java/org/orekit/bodies/OneAxisEllipsoid.java b/src/main/java/org/orekit/bodies/OneAxisEllipsoid.java index 62bdbc4aff..a93266c51f 100644 --- a/src/main/java/org/orekit/bodies/OneAxisEllipsoid.java +++ b/src/main/java/org/orekit/bodies/OneAxisEllipsoid.java @@ -77,7 +77,7 @@ public class OneAxisEllipsoid implements BodyShape { /** Simple constructor. * <p>The following table provides conventional parameters for global Earth models:</p> * <table border="1" cellpadding="5"> - * <tr bgcolor="#ccccff"><font size="+3"><th>model</th><th>a<sub>e</sub> (m)</th><th>f</th></font></tr> + * <tr bgcolor="#ccccff"><th>model</th><th>a<sub>e</sub> (m)</th><th>f</th></tr> * <tr><td bgcolor="#eeeeff">GRS 80</td><td>6378137.0</td><td>1.0 / 298.257222101</td></tr> * <tr><td bgcolor="#eeeeff">WGS84</td><td>6378137.0</td><td>1.0 / 298.257223563</td></tr> * </table> diff --git a/src/main/java/org/orekit/errors/OrekitMessages.java b/src/main/java/org/orekit/errors/OrekitMessages.java index f72a99bdd2..389193c542 100644 --- a/src/main/java/org/orekit/errors/OrekitMessages.java +++ b/src/main/java/org/orekit/errors/OrekitMessages.java @@ -54,7 +54,7 @@ public enum OrekitMessages implements Localizable { FRAME_ANCESTOR_OF_BOTH_FRAMES("frame {0} is an ancestor of both frames {1} and {2}"), FRAME_ANCESTOR_OF_NEITHER_FRAME("frame {0} is an ancestor of neither frame {1} nor {2}"), UNSUPPORTED_LOCAL_ORBITAL_FRAME("unsupported local orbital frame, supported types: {0} and {1}"), - NON_QUASI_INERTIAL_FRAME_NOT_SUITABLE_FOR_DEFINING_ORBITS("non quasi-inertial frame \"{0}\" is not suitable for defining orbits"), + NON_PSEUDO_INERTIAL_FRAME_NOT_SUITABLE_FOR_DEFINING_ORBITS("non pseudo-inertial frame \"{0}\" is not suitable for defining orbits"), DATA_ROOT_DIRECTORY_DOESN_NOT_EXISTS("data root directory {0} does not exist"), NOT_A_DIRECTORY("{0} is not a directory"), NEITHER_DIRECTORY_NOR_ZIP_OR_JAR("{0} is neither a directory nor a zip/jar archive file"), @@ -117,6 +117,14 @@ public enum OrekitMessages implements Localizable { OUT_OF_RANGE_EPHEMERIDES_DATE("out of range date for ephemerides: {0}, [{1}, {2}]"), UNEXPECTED_TWO_ELEVATION_VALUES_FOR_ONE_AZIMUTH("unexpected two elevation values: {0} and {1}, for one azimuth: {2}"), UNKNOWN_PARAMETER("unknown parameter {0}"), + UNSUPPORTED_PARAMETER_1_2("unsupported parameter name {0}: supported names {1}, {2}"), + UNKNOWN_ADDITIONAL_EQUATION("unknown additional equation"), + PARTIAL_DERIVATIVES_ONLY_IN_CARTESIAN("partial derivatives can be propagated only in cartesian parameters type"), + STATE_JACOBIAN_NEITHER_6X6_NOR_7X7("state jacobian is a {0}x{1} matrix, it should be either a 6x6 or a 7x7 matrix"), + STATE_AND_PARAMETERS_JACOBIANS_ROWS_MISMATCH("state jacobian has {0} rows but parameters jacobian has {1} rows"), + INITIAL_MATRIX_AND_PARAMETERS_NUMBER_MISMATCH("initial jacobian matrix has {0} columns, but {1} parameters have been selected"), + ORBIT_A_E_MISMATCH_WITH_CONIC_TYPE("orbit should be either elliptic with a > 0 and e < 1 or hyperbolic with a < 0 and e > 1, a = {0}, e = {1}"), + HYPERBOLIC_ORBIT_NOT_HANDLED_AS("hyperbolic orbits cannot be handled as {0} instances"), CCSDS_DATE_INVALID_PREAMBLE_FIELD("invalid preamble field in CCSDS date: {0}"), CCSDS_DATE_INVALID_LENGTH_TIME_FIELD("invalid time field length in CCSDS date: {0}, expected {1}"), CCSDS_DATE_MISSING_AGENCY_EPOCH("missing agency epoch in CCSDS date"), diff --git a/src/main/java/org/orekit/forces/AbstractParameterizable.java b/src/main/java/org/orekit/forces/AbstractParameterizable.java new file mode 100644 index 0000000000..159fb4cc7d --- /dev/null +++ b/src/main/java/org/orekit/forces/AbstractParameterizable.java @@ -0,0 +1,89 @@ +/* Copyright 2010 Centre National d'Études Spatiales + * Licensed to CS Communication & Systèmes (CS) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * CS licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.orekit.forces; + +import java.util.ArrayList; +import java.util.Collection; + +import org.orekit.errors.OrekitException; +import org.orekit.errors.OrekitMessages; + +/** Simple abstract class providing boilerplate parameters list. + * + * @author Luc Maisonobe + * @version $Revision$ $Date$ + */ + +public abstract class AbstractParameterizable implements Parameterizable { + + /** Serializable UID. */ + private static final long serialVersionUID = 3408804493769459294L; + + /** List of the parameters names. */ + private final Collection<String> parametersNames; + + /** Simple constructor. + * @param names names of the supported parameters + */ + protected AbstractParameterizable(final String ... names) { + parametersNames = new ArrayList<String>(); + for (final String name : names) { + parametersNames.add(name); + } + } + + /** Simple constructor. + * @param names names of the supported parameters + */ + protected AbstractParameterizable(final Collection<String> names) { + parametersNames = new ArrayList<String>(); + parametersNames.addAll(names); + } + + /** {@inheritDoc} */ + public Collection<String> getParametersNames() { + return parametersNames; + } + + /** {@inheritDoc} */ + public boolean isSupported(final String name) { + for (final String supportedName : parametersNames) { + if (supportedName.equals(name)) { + return true; + } + } + return false; + } + + /** Check if a parameter is supported and throw an IllegalArgumentException if not. + * @param name name of the parameter to check + * @exception IllegalArgumentException if the parameter is not supported + * @see #isSupported(String) + */ + public void complainIfNotSupported(final String name) throws IllegalArgumentException { + if (!isSupported(name)) { + throw OrekitException.createIllegalArgumentException(OrekitMessages.UNKNOWN_PARAMETER, name); + } + } + + /** {@inheritDoc} */ + public abstract double getParameter(String name) throws IllegalArgumentException; + + /** {@inheritDoc} */ + public abstract void setParameter(String name, double value) throws IllegalArgumentException; + +} diff --git a/src/main/java/org/orekit/forces/BoxAndSolarArraySpacecraft.java b/src/main/java/org/orekit/forces/BoxAndSolarArraySpacecraft.java index 55d4a950da..2841c70c1b 100644 --- a/src/main/java/org/orekit/forces/BoxAndSolarArraySpacecraft.java +++ b/src/main/java/org/orekit/forces/BoxAndSolarArraySpacecraft.java @@ -490,4 +490,11 @@ public class BoxAndSolarArraySpacecraft implements RadiationSensitive, DragSensi return dragCoeff; } + /** {@inheritDoc} */ + public void addDAccDParam(final Vector3D acceleration, final String paramName, final double[] dAccdParam) + throws OrekitException { + // TODO: not supported yet + throw OrekitException.createInternalError(null); + } + } diff --git a/src/main/java/org/orekit/forces/ForceModel.java b/src/main/java/org/orekit/forces/ForceModel.java index f972e2ef4f..e3d95fb975 100644 --- a/src/main/java/org/orekit/forces/ForceModel.java +++ b/src/main/java/org/orekit/forces/ForceModel.java @@ -16,8 +16,6 @@ */ package org.orekit.forces; -import java.io.Serializable; - import org.orekit.errors.OrekitException; import org.orekit.propagation.SpacecraftState; import org.orekit.propagation.events.EventDetector; @@ -53,7 +51,7 @@ import org.orekit.propagation.numerical.TimeDerivativesEquations; * @author Véronique Pommier-Maurussane * @version $Revision$ $Date$ */ -public interface ForceModel extends Serializable { +public interface ForceModel extends Parameterizable { /** Compute the contribution of the force model to the perturbing * acceleration. diff --git a/src/main/java/org/orekit/forces/ForceModelWithJacobians.java b/src/main/java/org/orekit/forces/ForceModelWithJacobians.java deleted file mode 100644 index 6a581ffd51..0000000000 --- a/src/main/java/org/orekit/forces/ForceModelWithJacobians.java +++ /dev/null @@ -1,47 +0,0 @@ -/* Copyright 2002-2010 CS Communication & Systèmes - * Licensed to CS Communication & Systèmes (CS) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * CS licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.orekit.forces; - -import org.orekit.errors.OrekitException; -import org.orekit.propagation.SpacecraftState; -import org.orekit.propagation.numerical.TimeDerivativesEquationsWithJacobians; - -/** This interface represents a parameterized force modifying spacecraft motion. - * - * <p> Objects implementing this interface are intended to be added, before the propagation is started, - * to a {@link org.orekit.propagation.numerical.NumericalPropagatorWithJacobians numerical propagator} - * in order to compute partial derivatives of orbital parameters with respect to the - * {@link org.orekit.propagation.numerical.NumericalPropagatorWithJacobians#selectParameters(String[]) selected} - * force model parameters.</p> - * - * @see ForceModel - * - * @author Pascal Parraud - * @version $Revision$ $Date$ - */ -public interface ForceModelWithJacobians extends Parameterizable, ForceModel { - - /** Compute the contribution of the force model to the perturbing - * acceleration and to the jacobians. - * @param s current state information: date, kinematics, attitude - * @param adder object where the contribution should be added - * @exception OrekitException if some specific error occurs - */ - void addContributionWithJacobians(SpacecraftState s, TimeDerivativesEquationsWithJacobians adder) - throws OrekitException; - -} diff --git a/src/main/java/org/orekit/forces/Parameterizable.java b/src/main/java/org/orekit/forces/Parameterizable.java index 7fec1641c9..35eccb5463 100644 --- a/src/main/java/org/orekit/forces/Parameterizable.java +++ b/src/main/java/org/orekit/forces/Parameterizable.java @@ -16,6 +16,7 @@ */ package org.orekit.forces; +import java.io.Serializable; import java.util.Collection; /** This interface enables to process partial derivatives @@ -25,13 +26,24 @@ import java.util.Collection; * @version $Revision$ $Date$ */ -public interface Parameterizable { +public interface Parameterizable extends Serializable { /** Get the names of the supported parameters for partial derivatives processing. * @return parameters names + * @see #isSupported(String) */ Collection<String> getParametersNames(); + /** Check if a parameter is supported. + * <p> + * Supported parameters are the ones that are listed in {@link #getParametersNames()} + * </p> + * @param name parameter name to check + * @return true if the parameter is supported + * @see #getParametersNames() + */ + boolean isSupported(String name); + /** Get parameter value from its name. * @param name parameter name * @return parameter value diff --git a/src/main/java/org/orekit/forces/SphericalSpacecraft.java b/src/main/java/org/orekit/forces/SphericalSpacecraft.java index 0429606e6f..d7c41853bf 100644 --- a/src/main/java/org/orekit/forces/SphericalSpacecraft.java +++ b/src/main/java/org/orekit/forces/SphericalSpacecraft.java @@ -17,8 +17,11 @@ package org.orekit.forces; import org.apache.commons.math.geometry.Vector3D; +import org.orekit.errors.OrekitException; +import org.orekit.errors.OrekitMessages; import org.orekit.forces.drag.DragSensitive; import org.orekit.forces.radiation.RadiationSensitive; +import org.orekit.forces.radiation.SolarRadiationPressure; import org.orekit.propagation.SpacecraftState; /** This class represents the features of a simplified spacecraft. @@ -129,7 +132,41 @@ public class SphericalSpacecraft implements RadiationSensitive, DragSensitive { /** Set kP value. */ private void setKP() { - kP = crossSection * (1 + 4 * (1.0 - absorptionCoeff) * (1.0 - specularReflectionCoeff) / 9); + kP = crossSection * (1 + 4 * (1.0 - absorptionCoeff) * (1.0 - specularReflectionCoeff) / 9.0); + } + + /** Computes kP derivative with respect to absorption coefficient. + * @return kP derivative with respect to absorption coefficient*/ + private double computeDKPDCa() { + return -4 * crossSection * (1.0 - specularReflectionCoeff) / 9; + } + + /** Computes kP derivative with respect to reflection coefficient. + * @return kP derivative with respect to reflection coefficient*/ + private double computeDKPDCr() { + return -4 * crossSection * (1.0 - absorptionCoeff) / 9; + } + + /** {@inheritDoc} */ + public void addDAccDParam(final Vector3D acceleration, final String paramName, final double[] dAccdParam) + throws OrekitException { + + final double coefficient; + if (paramName.equals(SolarRadiationPressure.ABSORPTION_COEFFICIENT)) { + coefficient = computeDKPDCa() / kP; + } else if (paramName.equals(SolarRadiationPressure.REFLECTION_COEFFICIENT)) { + coefficient = computeDKPDCr() / kP; + } else { + throw OrekitException.createIllegalArgumentException(OrekitMessages.UNSUPPORTED_PARAMETER_1_2, + paramName, + SolarRadiationPressure.ABSORPTION_COEFFICIENT, + SolarRadiationPressure.REFLECTION_COEFFICIENT); + } + + dAccdParam[0] += coefficient * acceleration.getX(); + dAccdParam[1] += coefficient * acceleration.getY(); + dAccdParam[2] += coefficient * acceleration.getZ(); + } } diff --git a/src/main/java/org/orekit/forces/drag/DragForce.java b/src/main/java/org/orekit/forces/drag/DragForce.java index 16035827e0..c6f9b15b75 100644 --- a/src/main/java/org/orekit/forces/drag/DragForce.java +++ b/src/main/java/org/orekit/forces/drag/DragForce.java @@ -22,12 +22,11 @@ import java.util.Collection; import org.apache.commons.math.geometry.Vector3D; import org.orekit.errors.OrekitException; import org.orekit.errors.OrekitMessages; -import org.orekit.forces.ForceModelWithJacobians; +import org.orekit.forces.Parameterizable; import org.orekit.frames.Frame; import org.orekit.propagation.SpacecraftState; import org.orekit.propagation.events.EventDetector; import org.orekit.propagation.numerical.TimeDerivativesEquations; -import org.orekit.propagation.numerical.TimeDerivativesEquationsWithJacobians; import org.orekit.time.AbsoluteDate; @@ -47,13 +46,20 @@ import org.orekit.time.AbsoluteDate; * @version $Revision:1665 $ $Date:2008-06-11 12:12:59 +0200 (mer., 11 juin 2008) $ */ -public class DragForce implements ForceModelWithJacobians { +public class DragForce implements Parameterizable { /** Parameter name for drag coefficient enabling jacobian processing. */ public static final String DRAG_COEFFICIENT = "DRAG COEFFICIENT"; + /** List of the parameters names. */ + private static final ArrayList<String> PARAMETERS_NAMES; + static { + PARAMETERS_NAMES = new ArrayList<String>(); + PARAMETERS_NAMES.add(DRAG_COEFFICIENT); + } + /** Serializable UID. */ - private static final long serialVersionUID = 2574653656986559955L; + private static final long serialVersionUID = 5386256916056674950L; /** Atmospheric model. */ private final Atmosphere atmosphere; @@ -61,9 +67,6 @@ public class DragForce implements ForceModelWithJacobians { /** Spacecraft. */ private final DragSensitive spacecraft; - /** List of the parameters names. */ - private final ArrayList<String> parametersNames = new ArrayList<String>(); - /** Simple constructor. * @param atmosphere atmospheric model * @param spacecraft the object physical and geometrical information @@ -71,7 +74,6 @@ public class DragForce implements ForceModelWithJacobians { public DragForce(final Atmosphere atmosphere, final DragSensitive spacecraft) { this.atmosphere = atmosphere; this.spacecraft = spacecraft; - this.parametersNames.add(DRAG_COEFFICIENT); } /** Compute the contribution of the drag to the perturbing acceleration. @@ -104,14 +106,13 @@ public class DragForce implements ForceModelWithJacobians { } /** {@inheritDoc} */ - public void addContributionWithJacobians(final SpacecraftState s, - final TimeDerivativesEquationsWithJacobians adder) - throws OrekitException { + public Collection<String> getParametersNames() { + return PARAMETERS_NAMES; } /** {@inheritDoc} */ - public Collection<String> getParametersNames() { - return parametersNames; + public boolean isSupported(final String name) { + return name.equals(DRAG_COEFFICIENT); } /** {@inheritDoc} */ diff --git a/src/main/java/org/orekit/forces/gravity/CunninghamAttractionModel.java b/src/main/java/org/orekit/forces/gravity/CunninghamAttractionModel.java index 75784896b0..c40768939e 100644 --- a/src/main/java/org/orekit/forces/gravity/CunninghamAttractionModel.java +++ b/src/main/java/org/orekit/forces/gravity/CunninghamAttractionModel.java @@ -21,6 +21,7 @@ import org.apache.commons.math.geometry.Vector3D; import org.apache.commons.math.util.FastMath; import org.orekit.errors.OrekitException; import org.orekit.errors.OrekitMessages; +import org.orekit.forces.AbstractParameterizable; import org.orekit.forces.ForceModel; import org.orekit.frames.Frame; import org.orekit.frames.Transform; @@ -42,10 +43,10 @@ import org.orekit.propagation.numerical.TimeDerivativesEquations; * @version $Revision:1665 $ $Date:2008-06-11 12:12:59 +0200 (mer., 11 juin 2008) $ */ -public class CunninghamAttractionModel implements ForceModel { +public class CunninghamAttractionModel extends AbstractParameterizable implements ForceModel { /** Serializable UID. */ - private static final long serialVersionUID = 2609845747545125479L; + private static final long serialVersionUID = 759122284106467933L; /** Equatorial radius of the Central Body. */ private final double equatorialRadius; @@ -81,6 +82,7 @@ public class CunninghamAttractionModel implements ForceModel { final double equatorialRadius, final double mu, final double[][] C, final double[][] S) throws IllegalArgumentException { + super("central attraction coefficient"); this.bodyFrame = centralBodyFrame; this.equatorialRadius = equatorialRadius; @@ -375,4 +377,18 @@ public class CunninghamAttractionModel implements ForceModel { return new EventDetector[0]; } + /** {@inheritDoc} */ + public double getParameter(final String name) + throws IllegalArgumentException { + complainIfNotSupported(name); + return mu; + } + + /** {@inheritDoc} */ + public void setParameter(final String name, final double value) + throws IllegalArgumentException { + complainIfNotSupported(name); + mu = value; + } + } diff --git a/src/main/java/org/orekit/forces/gravity/DrozinerAttractionModel.java b/src/main/java/org/orekit/forces/gravity/DrozinerAttractionModel.java index 3218ff9986..2469fd4b44 100644 --- a/src/main/java/org/orekit/forces/gravity/DrozinerAttractionModel.java +++ b/src/main/java/org/orekit/forces/gravity/DrozinerAttractionModel.java @@ -20,6 +20,7 @@ import org.apache.commons.math.geometry.Vector3D; import org.apache.commons.math.util.FastMath; import org.orekit.errors.OrekitException; import org.orekit.errors.OrekitMessages; +import org.orekit.forces.AbstractParameterizable; import org.orekit.forces.ForceModel; import org.orekit.frames.Frame; import org.orekit.frames.Transform; @@ -39,10 +40,10 @@ import org.orekit.propagation.numerical.TimeDerivativesEquations; * @version $Revision:1665 $ $Date:2008-06-11 12:12:59 +0200 (mer., 11 juin 2008) $ */ -public class DrozinerAttractionModel implements ForceModel { +public class DrozinerAttractionModel extends AbstractParameterizable implements ForceModel { /** Serializable UID. */ - private static final long serialVersionUID = 9117000158528461356L; + private static final long serialVersionUID = -6897768625006106349L; /** Reference equatorial radius of the potential. */ private final double equatorialRadius; @@ -79,6 +80,7 @@ public class DrozinerAttractionModel implements ForceModel { final double[][] C, final double[][] S) throws IllegalArgumentException { + super("central attraction coefficient"); this.centralBodyFrame = centralBodyFrame; this.equatorialRadius = equatorialRadius; this.mu = mu; @@ -274,4 +276,18 @@ public class DrozinerAttractionModel implements ForceModel { return new EventDetector[0]; } + /** {@inheritDoc} */ + public double getParameter(final String name) + throws IllegalArgumentException { + complainIfNotSupported(name); + return mu; + } + + /** {@inheritDoc} */ + public void setParameter(final String name, final double value) + throws IllegalArgumentException { + complainIfNotSupported(name); + mu = value; + } + } diff --git a/src/main/java/org/orekit/forces/gravity/NewtonianAttraction.java b/src/main/java/org/orekit/forces/gravity/NewtonianAttraction.java new file mode 100644 index 0000000000..1bddd9aa07 --- /dev/null +++ b/src/main/java/org/orekit/forces/gravity/NewtonianAttraction.java @@ -0,0 +1,127 @@ +/* Copyright 2010 Centre National d'Études Spatiales + * Licensed to CS Communication & Systèmes (CS) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * CS licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.orekit.forces.gravity; + +import org.apache.commons.math.geometry.Vector3D; +import org.apache.commons.math.util.FastMath; +import org.orekit.errors.OrekitException; +import org.orekit.forces.AbstractParameterizable; +import org.orekit.forces.ForceModel; +import org.orekit.propagation.SpacecraftState; +import org.orekit.propagation.events.EventDetector; +import org.orekit.propagation.numerical.AccelerationJacobiansProvider; +import org.orekit.propagation.numerical.TimeDerivativesEquations; + +/** Force model for Newtonian central body attraction. + * @author Luc Maisonobe + * @version $Revision$ $Date$ + */ +public class NewtonianAttraction extends AbstractParameterizable implements AccelerationJacobiansProvider, ForceModel { + + /** Name of the single parameter of this model: the central attraction coefficient. */ + public static final String CENTRAL_ATTRACTION_COEFFICIENT = "central attraction coefficient"; + + /** Serializable UID. */ + private static final long serialVersionUID = -7754312556095545327L; + + /** Central attraction coefficient (m^3/s^2). */ + private double mu; + + /** Simple constructor. + * @param mu central attraction coefficient (m^3/s^2) + */ + public NewtonianAttraction(final double mu) { + super(CENTRAL_ATTRACTION_COEFFICIENT); + this.mu = mu; + } + + /** {@inheritDoc} */ + public void addDAccDState(final SpacecraftState s, + final double[][] dAccdPos, final double[][] dAccdVel, final double[] dAccdM) + throws OrekitException { + + final Vector3D position = s.getPVCoordinates().getPosition(); + final double r2 = position.getNormSq(); + final Vector3D acceleration = new Vector3D(-mu / (r2 * FastMath.sqrt(r2)), position); + + final double x2 = position.getX() * position.getX(); + final double y2 = position.getY() * position.getY(); + final double z2 = position.getZ() * position.getZ(); + final double xy = position.getX() * position.getY(); + final double yz = position.getY() * position.getZ(); + final double zx = position.getZ() * position.getX(); + final double prefix = -Vector3D.dotProduct(acceleration, position) / (r2 * r2); + + // the only non-null contribution for this force is on dAcc/dPos + dAccdPos[0][0] += prefix * (2 * x2 - y2 - z2); + dAccdPos[0][1] += prefix * 3 * xy; + dAccdPos[0][2] += prefix * 3 * zx; + dAccdPos[1][0] += prefix * 3 * xy; + dAccdPos[1][1] += prefix * (2 * y2 - z2 - x2); + dAccdPos[1][2] += prefix * 3 * yz; + dAccdPos[2][0] += prefix * 3 * zx; + dAccdPos[2][1] += prefix * 3 * yz; + dAccdPos[2][2] += prefix * (2 * z2 - x2 - y2); + + } + + /** {@inheritDoc} */ + public void addDAccDParam(final SpacecraftState s, final String paramName, final double[] dAccdParam) + throws OrekitException { + complainIfNotSupported(paramName); + final Vector3D position = s.getPVCoordinates().getPosition(); + final double r2 = position.getNormSq(); + final double factor = -1.0 / (r2 * FastMath.sqrt(r2)); + dAccdParam[0] += factor * position.getX(); + dAccdParam[1] += factor * position.getY(); + dAccdParam[2] += factor * position.getZ(); + } + + /** Get the central attraction coefficient μ. + * @return mu central attraction coefficient (m<sup>3</sup>/s<sup>2</sup>) + */ + public double getMu() { + return mu; + } + + /** {@inheritDoc} */ + public double getParameter(final String name) + throws IllegalArgumentException { + complainIfNotSupported(name); + return mu; + } + + /** {@inheritDoc} */ + public void setParameter(final String name, final double value) + throws IllegalArgumentException { + complainIfNotSupported(name); + this.mu = value; + } + + /** {@inheritDoc} */ + public void addContribution(final SpacecraftState s, final TimeDerivativesEquations adder) + throws OrekitException { + adder.addKeplerContribution(mu); + } + + /** {@inheritDoc} */ + public EventDetector[] getEventsDetectors() { + return new EventDetector[0]; + } + +} + diff --git a/src/main/java/org/orekit/forces/gravity/ThirdBodyAttraction.java b/src/main/java/org/orekit/forces/gravity/ThirdBodyAttraction.java index 7b806383b6..b99b3d4f9c 100644 --- a/src/main/java/org/orekit/forces/gravity/ThirdBodyAttraction.java +++ b/src/main/java/org/orekit/forces/gravity/ThirdBodyAttraction.java @@ -20,6 +20,7 @@ import org.apache.commons.math.geometry.Vector3D; import org.apache.commons.math.util.FastMath; import org.orekit.bodies.CelestialBody; import org.orekit.errors.OrekitException; +import org.orekit.forces.AbstractParameterizable; import org.orekit.forces.ForceModel; import org.orekit.propagation.SpacecraftState; import org.orekit.propagation.events.EventDetector; @@ -32,21 +33,26 @@ import org.orekit.propagation.numerical.TimeDerivativesEquations; * @author Véronique Pommier-Maurussane * @version $Revision:1665 $ $Date:2008-06-11 12:12:59 +0200 (mer., 11 juin 2008) $ */ -public class ThirdBodyAttraction implements ForceModel { +public class ThirdBodyAttraction extends AbstractParameterizable implements ForceModel { /** Serializable UID. */ - private static final long serialVersionUID = 9017402538195695004L; + private static final long serialVersionUID = -1703641239448217284L; /** The body to consider. */ private final CelestialBody body; + /** Local value for body attraction coefficient. */ + private double gm; + /** Simple constructor. * @param body the third body to consider * (ex: {@link org.orekit.bodies.CelestialBodyFactory#getSun()} or * {@link org.orekit.bodies.CelestialBodyFactory#getMoon()}) */ public ThirdBodyAttraction(final CelestialBody body) { + super(body.getName() + " attraction coefficient"); this.body = body; + this.gm = body.getGM(); } /** {@inheritDoc} */ @@ -61,8 +67,8 @@ public class ThirdBodyAttraction implements ForceModel { // compute relative acceleration final Vector3D gamma = - new Vector3D(body.getGM() * FastMath.pow(r2Sat, -1.5), satToBody, - -body.getGM() * FastMath.pow(r2Central, -1.5), centralToBody); + new Vector3D(gm * FastMath.pow(r2Sat, -1.5), satToBody, + -gm * FastMath.pow(r2Central, -1.5), centralToBody); // add contribution to the ODE second member adder.addXYZAcceleration(gamma.getX(), gamma.getY(), gamma.getZ()); @@ -74,4 +80,18 @@ public class ThirdBodyAttraction implements ForceModel { return new EventDetector[0]; } + /** {@inheritDoc} */ + public double getParameter(final String name) + throws IllegalArgumentException { + complainIfNotSupported(name); + return gm; + } + + /** {@inheritDoc} */ + public void setParameter(final String name, final double value) + throws IllegalArgumentException { + complainIfNotSupported(name); + gm = value; + } + } diff --git a/src/main/java/org/orekit/forces/gravity/potential/SHMFormatReader.java b/src/main/java/org/orekit/forces/gravity/potential/SHMFormatReader.java index 78fc9492f7..7002869b6e 100644 --- a/src/main/java/org/orekit/forces/gravity/potential/SHMFormatReader.java +++ b/src/main/java/org/orekit/forces/gravity/potential/SHMFormatReader.java @@ -68,7 +68,8 @@ public class SHMFormatReader extends PotentialCoefficientsReader { boolean okSHM = false; boolean okCoeffs = false; String line = r.readLine(); - if ("FIRST ".equals(line.substring(0, 6)) && + if ((line != null) && + "FIRST ".equals(line.substring(0, 6)) && "SHM ".equals(line.substring(49, 56))) { for (line = r.readLine(); line != null; line = r.readLine()) { if (line.length() >= 6) { diff --git a/src/main/java/org/orekit/forces/maneuvers/ConstantThrustManeuver.java b/src/main/java/org/orekit/forces/maneuvers/ConstantThrustManeuver.java index ff20bbb216..6b140a081d 100644 --- a/src/main/java/org/orekit/forces/maneuvers/ConstantThrustManeuver.java +++ b/src/main/java/org/orekit/forces/maneuvers/ConstantThrustManeuver.java @@ -18,6 +18,7 @@ package org.orekit.forces.maneuvers; import org.apache.commons.math.geometry.Vector3D; import org.orekit.errors.OrekitException; +import org.orekit.forces.AbstractParameterizable; import org.orekit.forces.ForceModel; import org.orekit.propagation.SpacecraftState; import org.orekit.propagation.events.DateDetector; @@ -37,11 +38,17 @@ import org.orekit.time.AbsoluteDate; * @author Luc Maisonobe * @version $Revision:1665 $ $Date:2008-06-11 12:12:59 +0200 (mer., 11 juin 2008) $ */ -public class ConstantThrustManeuver implements ForceModel { +public class ConstantThrustManeuver extends AbstractParameterizable implements ForceModel { /** Reference gravity acceleration constant (m/s<sup>2</sup>). */ public static final double G0 = 9.80665; + /** Parameter name for thrust. */ + private static final String THRUST = "thrust"; + + /** Parameter name for flow rate. */ + private static final String FLOW_RATE = "flow rate"; + /** Serializable UID. */ private static final long serialVersionUID = 5349622732741384211L; @@ -55,10 +62,10 @@ public class ConstantThrustManeuver implements ForceModel { private final AbsoluteDate endDate; /** Engine thrust. */ - private final double thrust; + private double thrust; /** Engine flow-rate. */ - private final double flowRate; + private double flowRate; /** Direction of the acceleration in satellite frame. */ private final Vector3D direction; @@ -75,6 +82,7 @@ public class ConstantThrustManeuver implements ForceModel { final double thrust, final double isp, final Vector3D direction) { + super(THRUST, FLOW_RATE); if (duration >= 0) { this.startDate = date; this.endDate = date.shiftedBy(duration); @@ -115,6 +123,27 @@ public class ConstantThrustManeuver implements ForceModel { }; } + /** {@inheritDoc} */ + public double getParameter(final String name) + throws IllegalArgumentException { + complainIfNotSupported(name); + if (name.equals(THRUST)) { + return thrust; + } + return flowRate; + } + + /** {@inheritDoc} */ + public void setParameter(final String name, final double value) + throws IllegalArgumentException { + complainIfNotSupported(name); + if (name.equals(THRUST)) { + thrust = value; + } else { + flowRate = value; + } + } + /** Detector for start of maneuver. */ private class FiringStartDetector extends DateDetector { diff --git a/src/main/java/org/orekit/forces/package.html b/src/main/java/org/orekit/forces/package.html index 53c91fd7f2..864d2d8dc6 100644 --- a/src/main/java/org/orekit/forces/package.html +++ b/src/main/java/org/orekit/forces/package.html @@ -1,12 +1,11 @@ <html> <body> This package provides the interface for force models that will be used by the -{@link org.orekit.propagation.numerical.NumericalPropagator} and the -{@link org.orekit.propagation.numerical.NumericalPropagatorWithJacobians}, as well as +{@link org.orekit.propagation.numerical.NumericalPropagator}, as well as some classical spacecraft models for surface forces (spherical, box and solar array ...). @author Luc Maisonobe @author Pascal Parraud </body> -</html> \ No newline at end of file +</html> diff --git a/src/main/java/org/orekit/forces/radiation/RadiationSensitive.java b/src/main/java/org/orekit/forces/radiation/RadiationSensitive.java index ae49115fd2..b111d33eba 100644 --- a/src/main/java/org/orekit/forces/radiation/RadiationSensitive.java +++ b/src/main/java/org/orekit/forces/radiation/RadiationSensitive.java @@ -64,4 +64,13 @@ public interface RadiationSensitive extends Serializable { */ double getReflectionCoefficient(); + /** Compute acceleration derivatives with respect to additional parameters. + * @param acceleration spacecraft acceleration + * @param paramName name of the parameter with respect to which derivatives are required + * @param dAccdParam acceleration derivatives with respect to additional parameters + * @exception OrekitException if derivatives cannot be computed + */ + void addDAccDParam(Vector3D acceleration, String paramName, double[] dAccdParam) + throws OrekitException; + } diff --git a/src/main/java/org/orekit/forces/radiation/SolarRadiationPressure.java b/src/main/java/org/orekit/forces/radiation/SolarRadiationPressure.java index 12ff8a86ab..b89717d77d 100644 --- a/src/main/java/org/orekit/forces/radiation/SolarRadiationPressure.java +++ b/src/main/java/org/orekit/forces/radiation/SolarRadiationPressure.java @@ -16,20 +16,18 @@ */ package org.orekit.forces.radiation; -import java.util.ArrayList; -import java.util.Collection; - import org.apache.commons.math.geometry.Vector3D; import org.apache.commons.math.util.FastMath; import org.orekit.errors.OrekitException; import org.orekit.errors.OrekitMessages; -import org.orekit.forces.ForceModelWithJacobians; +import org.orekit.forces.AbstractParameterizable; +import org.orekit.forces.ForceModel; import org.orekit.frames.Frame; import org.orekit.propagation.SpacecraftState; import org.orekit.propagation.events.AbstractDetector; import org.orekit.propagation.events.EventDetector; +import org.orekit.propagation.numerical.AccelerationJacobiansProvider; import org.orekit.propagation.numerical.TimeDerivativesEquations; -import org.orekit.propagation.numerical.TimeDerivativesEquationsWithJacobians; import org.orekit.time.AbsoluteDate; import org.orekit.utils.PVCoordinates; import org.orekit.utils.PVCoordinatesProvider; @@ -42,18 +40,18 @@ import org.orekit.utils.PVCoordinatesProvider; * @author Pascal Parraud * @version $Revision:1665 $ $Date:2008-06-11 12:12:59 +0200 (mer., 11 juin 2008) $ */ -public class SolarRadiationPressure implements ForceModelWithJacobians { +public class SolarRadiationPressure extends AbstractParameterizable implements ForceModel, AccelerationJacobiansProvider { - /** Parameter name for absorption coefficient enabling jacobian processing. */ - public static final String ABSORPTION_COEFFICIENT = "ABSORPTION COEFFICIENT"; + /** Parameter name for absorption coefficient. */ + public static final String ABSORPTION_COEFFICIENT = "absorption coefficient"; - /** Parameter name for specular reflection coefficient enabling jacobian processing. */ - public static final String REFLECTION_COEFFICIENT = "REFLECTION COEFFICIENT"; + /** Parameter name for reflection coefficient. */ + public static final String REFLECTION_COEFFICIENT = "reflection coefficient"; /** Serializable UID. */ - private static final long serialVersionUID = 8874297900604482921L; + private static final long serialVersionUID = -4510170320082379419L; - /** Sun radius (m). */ + /** Sun radius (m). */ private static final double SUN_RADIUS = 6.95e8; /** Reference flux normalized for a 1m distance (N). */ @@ -68,9 +66,6 @@ public class SolarRadiationPressure implements ForceModelWithJacobians { /** Spacecraft. */ private final RadiationSensitive spacecraft; - /** List of the parameters names. */ - private final ArrayList<String> parametersNames = new ArrayList<String>(); - /** Simple constructor with default reference values. * <p>When this constructor is used, the reference values are:</p> * <ul> @@ -102,30 +97,49 @@ public class SolarRadiationPressure implements ForceModelWithJacobians { final PVCoordinatesProvider sun, final double equatorialRadius, final RadiationSensitive spacecraft) { + super(ABSORPTION_COEFFICIENT, REFLECTION_COEFFICIENT); this.kRef = pRef * dRef * dRef; this.sun = sun; this.equatorialRadius = equatorialRadius; this.spacecraft = spacecraft; - this.parametersNames.add(ABSORPTION_COEFFICIENT); - this.parametersNames.add(REFLECTION_COEFFICIENT); } - /** {@inheritDoc} */ - public void addContribution(final SpacecraftState s, final TimeDerivativesEquations adder) - throws OrekitException { - + /** Compute radiation coefficient. + * @param s spacecraft state + * @return coefficient for acceleration computation + * @exception OrekitException if position cannot be computed + */ + private double computeRawP (final SpacecraftState s) throws OrekitException { final AbsoluteDate date = s.getDate(); final Frame frame = s.getFrame(); final Vector3D position = s.getPVCoordinates().getPosition(); - // raw radiation pressure final Vector3D satSunVector = getSatSunVector(s); final double r2 = satSunVector.getNormSq(); - final double rawP = kRef * getLightningRatio(position, frame, date) / r2; - final Vector3D flux = new Vector3D(-rawP / FastMath.sqrt(r2), satSunVector); + return kRef * getLightningRatio(position, frame, date) / r2; + } + + /** Compute radiation acceleration. + * @param s spacecraft state + * @return acceleration + * @exception OrekitException if position cannot be computed + */ + private Vector3D computeAcceleration(final SpacecraftState s) throws OrekitException { + + final Vector3D satSunVector = getSatSunVector(s); + final double r2 = satSunVector.getNormSq(); + + final double rawP = computeRawP(s); + // raw radiation pressure + return spacecraft.radiationPressureAcceleration(s, new Vector3D(-rawP / FastMath.sqrt(r2), satSunVector)); + } + + /** {@inheritDoc} */ + public void addContribution(final SpacecraftState s, final TimeDerivativesEquations adder) + throws OrekitException { // provide the perturbing acceleration to the derivatives adder - adder.addAcceleration(spacecraft.radiationPressureAcceleration(s, flux), frame); + adder.addAcceleration(computeAcceleration(s), s.getFrame()); } @@ -185,12 +199,8 @@ public class SolarRadiationPressure implements ForceModelWithJacobians { alpha2 * FastMath.sqrt(alphaEarth * alphaEarth - alpha2 * alpha2); result = (P1 - P2) / (FastMath.PI * alphaSun * alphaSun); - - } - return result; - } /** Compute sat-Sun vector in spacecraft state frame. @@ -213,37 +223,69 @@ public class SolarRadiationPressure implements ForceModelWithJacobians { } /** {@inheritDoc} */ - public void addContributionWithJacobians(final SpacecraftState s, - final TimeDerivativesEquationsWithJacobians adder) + public void addDAccDState(final SpacecraftState s, + final double[][] dAccdPos, final double[][] dAccdVel, final double[] dAccdM) throws OrekitException { + + final Vector3D satSunVector = getSatSunVector(s); + final double r2 = satSunVector.getNormSq(); + final double rawP = computeRawP(s); + final Vector3D acceleration = spacecraft.radiationPressureAcceleration(s, new Vector3D(-rawP / FastMath.sqrt(r2), satSunVector)); + + final double x2 = satSunVector.getX() * satSunVector.getX(); + final double y2 = satSunVector.getY() * satSunVector.getY(); + final double z2 = satSunVector.getZ() * satSunVector.getZ(); + final double xy = satSunVector.getX() * satSunVector.getY(); + final double yz = satSunVector.getY() * satSunVector.getZ(); + final double zx = satSunVector.getZ() * satSunVector.getX(); + final double prefix = Vector3D.dotProduct(acceleration, satSunVector) / (r2 * r2); + + // jacobian with respect to position + dAccdPos[0][0] += prefix * (2 * x2 - y2 - z2); + dAccdPos[0][1] += prefix * 3 * xy; + dAccdPos[0][2] += prefix * 3 * zx; + dAccdPos[1][0] += prefix * 3 * xy; + dAccdPos[1][1] += prefix * (2 * y2 - z2 - x2); + dAccdPos[1][2] += prefix * 3 * yz; + dAccdPos[2][0] += prefix * 3 * zx; + dAccdPos[2][1] += prefix * 3 * yz; + dAccdPos[2][2] += prefix * (2 * z2 - x2 - y2); + + // jacobian with respect to velocity is null + + if (dAccdM != null) { + // jacobian with respect to mass + dAccdM[0] -= acceleration.getX() / s.getMass(); + dAccdM[1] -= acceleration.getY() / s.getMass(); + dAccdM[2] -= acceleration.getZ() / s.getMass(); + } + } /** {@inheritDoc} */ - public Collection<String> getParametersNames() { - return parametersNames; + public void addDAccDParam(final SpacecraftState s, final String paramName, final double[] dAccdParam) + throws OrekitException { + spacecraft.addDAccDParam(computeAcceleration(s), paramName, dAccdParam); } /** {@inheritDoc} */ public double getParameter(final String name) throws IllegalArgumentException { - if (name.matches(ABSORPTION_COEFFICIENT)) { + complainIfNotSupported(name); + if (name.equals(ABSORPTION_COEFFICIENT)) { return spacecraft.getAbsorptionCoefficient(); - } else if (name.matches(REFLECTION_COEFFICIENT)) { - return spacecraft.getReflectionCoefficient(); - } else { - throw OrekitException.createIllegalArgumentException(OrekitMessages.UNKNOWN_PARAMETER, name); } + return spacecraft.getReflectionCoefficient(); } /** {@inheritDoc} */ public void setParameter(final String name, final double value) throws IllegalArgumentException { - if (name.matches(ABSORPTION_COEFFICIENT)) { + complainIfNotSupported(name); + if (name.equals(ABSORPTION_COEFFICIENT)) { spacecraft.setAbsorptionCoefficient(value); - } else if (name.matches(REFLECTION_COEFFICIENT)) { - spacecraft.setReflectionCoefficient(value); } else { - throw OrekitException.createIllegalArgumentException(OrekitMessages.UNKNOWN_PARAMETER, name); + spacecraft.setReflectionCoefficient(value); } } diff --git a/src/main/java/org/orekit/frames/Frame.java b/src/main/java/org/orekit/frames/Frame.java index 67f73be414..1bc68a3e82 100644 --- a/src/main/java/org/orekit/frames/Frame.java +++ b/src/main/java/org/orekit/frames/Frame.java @@ -64,7 +64,7 @@ public class Frame implements Serializable { private Transform transform; /** Map of deepest frames commons with other frames. */ - private final WeakHashMap<Frame, Frame> commons; + private final transient WeakHashMap<Frame, Frame> commons; /** Instance name. */ private final String name; diff --git a/src/main/java/org/orekit/frames/FramesFactory.java b/src/main/java/org/orekit/frames/FramesFactory.java index 01b4aa9e54..5438d21d13 100644 --- a/src/main/java/org/orekit/frames/FramesFactory.java +++ b/src/main/java/org/orekit/frames/FramesFactory.java @@ -149,7 +149,7 @@ public class FramesFactory implements Serializable { private static final long serialVersionUID = 1720647682459923909L; /** Predefined frames. */ - private static Map<Predefined, FactoryManagedFrame> FRAMES = + private static transient Map<Predefined, FactoryManagedFrame> FRAMES = new WeakHashMap<Predefined, FactoryManagedFrame>(); /** EOP 1980 loaders. */ diff --git a/src/main/java/org/orekit/frames/SpacecraftFrame.java b/src/main/java/org/orekit/frames/SpacecraftFrame.java index e533a8cf9f..105b226e77 100644 --- a/src/main/java/org/orekit/frames/SpacecraftFrame.java +++ b/src/main/java/org/orekit/frames/SpacecraftFrame.java @@ -37,7 +37,7 @@ public class SpacecraftFrame extends Frame implements PVCoordinatesProvider { private final Propagator propagator; /** Cached date to avoid useless computation. */ - private AbsoluteDate cachedDate; + private transient AbsoluteDate cachedDate; /** Simple constructor. * @param propagator orbit/attitude propagator computing spacecraft state evolution diff --git a/src/main/java/org/orekit/frames/TIRF2000Frame.java b/src/main/java/org/orekit/frames/TIRF2000Frame.java index 6fb3c8110b..b9d919ba2b 100644 --- a/src/main/java/org/orekit/frames/TIRF2000Frame.java +++ b/src/main/java/org/orekit/frames/TIRF2000Frame.java @@ -54,7 +54,7 @@ class TIRF2000Frame extends FactoryManagedFrame { private static final double ERA_1B = ERA_1A * 0.00273781191135448; /** Cached date to avoid useless calculus. */ - private AbsoluteDate cachedDate; + private transient AbsoluteDate cachedDate; /** Earth Rotation Angle, in radians. */ private double era; diff --git a/src/main/java/org/orekit/frames/VEISFrame.java b/src/main/java/org/orekit/frames/VEISFrame.java index e113db2b4b..143a3bb9ee 100644 --- a/src/main/java/org/orekit/frames/VEISFrame.java +++ b/src/main/java/org/orekit/frames/VEISFrame.java @@ -50,7 +50,7 @@ class VEISFrame extends FactoryManagedFrame { private static final double VSTD = 7.292115146705209e-5; /** Cached date to avoid useless calculus. */ - private AbsoluteDate cachedDate; + private transient AbsoluteDate cachedDate; /** Constructor for the singleton. * @param factoryKey key of the frame within the factory diff --git a/src/main/java/org/orekit/orbits/CartesianOrbit.java b/src/main/java/org/orekit/orbits/CartesianOrbit.java index af7740ef3e..8db09f7b5d 100644 --- a/src/main/java/org/orekit/orbits/CartesianOrbit.java +++ b/src/main/java/org/orekit/orbits/CartesianOrbit.java @@ -16,6 +16,9 @@ */ package org.orekit.orbits; +import org.apache.commons.math.geometry.Rotation; +import org.apache.commons.math.geometry.Vector3D; +import org.apache.commons.math.util.FastMath; import org.orekit.frames.Frame; import org.orekit.time.AbsoluteDate; import org.orekit.utils.PVCoordinates; @@ -59,10 +62,10 @@ import org.orekit.utils.PVCoordinates; public class CartesianOrbit extends Orbit { /** Serializable UID. */ - private static final long serialVersionUID = -6035381767203311530L; + private static final long serialVersionUID = -5411308212620896302L; - /** Underlying equinoctial orbit providing non-cartesian elements. */ - private final EquinoctialOrbit equinoctial; + /** Underlying equinoctial orbit to which high-level methods are delegated. */ + private transient EquinoctialOrbit equinoctial; /** Constructor from cartesian parameters. * @param pvCoordinates the position and velocity of the satellite. @@ -77,7 +80,7 @@ public class CartesianOrbit extends Orbit { final AbsoluteDate date, final double mu) throws IllegalArgumentException { super(pvCoordinates, frame, date, mu); - equinoctial = new EquinoctialOrbit(pvCoordinates, frame, date, mu); + equinoctial = null; } /** Constructor from any kind of orbital parameters. @@ -85,34 +88,56 @@ public class CartesianOrbit extends Orbit { */ public CartesianOrbit(final Orbit op) { super(op.getPVCoordinates(), op.getFrame(), op.getDate(), op.getMu()); - equinoctial = (op instanceof EquinoctialOrbit) ? (EquinoctialOrbit) op : new EquinoctialOrbit(op); + if (op instanceof EquinoctialOrbit) { + equinoctial = (EquinoctialOrbit) op; + } else if (op instanceof CartesianOrbit) { + equinoctial = ((CartesianOrbit) op).equinoctial; + } else { + equinoctial = null; + } + } + + /** Lazy evaluation of equinoctial parameters. */ + private void initEquinoctial() { + if (equinoctial == null) { + equinoctial = new EquinoctialOrbit(getPVCoordinates(), getFrame(), getDate(), getMu()); + } } /** Get the semi-major axis. * @return semi-major axis (m) */ public double getA() { - return equinoctial.getA(); + // lazy evaluation of semi-major axis + final double r = getPVCoordinates().getPosition().getNorm(); + final double V2 = getPVCoordinates().getVelocity().getNormSq(); + return r / (2 - r * V2 / getMu()); } /** Get the eccentricity. * @return eccentricity */ public double getE() { - return equinoctial.getE(); + final Vector3D pvP = getPVCoordinates().getPosition(); + final Vector3D pvV = getPVCoordinates().getVelocity(); + final double rV2OnMu = pvP.getNorm() * pvV.getNormSq() / getMu(); + final double eSE = Vector3D.dotProduct(pvP, pvV) / FastMath.sqrt(getMu() * getA()); + final double eCE = rV2OnMu - 1; + return FastMath.sqrt(eCE * eCE + eSE * eSE); } /** Get the inclination. * @return inclination (rad) */ public double getI() { - return equinoctial.getI(); + return Vector3D.angle(Vector3D.PLUS_K, getPVCoordinates().getMomentum()); } /** Get the first component of the eccentricity vector. * @return first component of the eccentricity vector */ public double getEquinoctialEx() { + initEquinoctial(); return equinoctial.getEquinoctialEx(); } @@ -120,6 +145,7 @@ public class CartesianOrbit extends Orbit { * @return second component of the eccentricity vector */ public double getEquinoctialEy() { + initEquinoctial(); return equinoctial.getEquinoctialEy(); } @@ -127,20 +153,31 @@ public class CartesianOrbit extends Orbit { * @return first component of the inclination vector. */ public double getHx() { - return equinoctial.getHx(); + final Vector3D w = getPVCoordinates().getMomentum().normalize(); + // Check for equatorial retrograde orbit + if (((w.getX() * w.getX() + w.getY() * w.getY()) == 0) && w.getZ() < 0) { + return Double.NaN; + } + return -w.getY() / (1 + w.getZ()); } /** Get the second component of the inclination vector. * @return second component of the inclination vector. */ public double getHy() { - return equinoctial.getHy(); + final Vector3D w = getPVCoordinates().getMomentum().normalize(); + // Check for equatorial retrograde orbit + if (((w.getX() * w.getX() + w.getY() * w.getY()) == 0) && w.getZ() < 0) { + return Double.NaN; + } + return w.getX() / (1 + w.getZ()); } /** Get the true latitude argument. * @return true latitude argument (rad) */ public double getLv() { + initEquinoctial(); return equinoctial.getLv(); } @@ -148,6 +185,7 @@ public class CartesianOrbit extends Orbit { * @return eccentric latitude argument.(rad) */ public double getLE() { + initEquinoctial(); return equinoctial.getLE(); } @@ -155,12 +193,185 @@ public class CartesianOrbit extends Orbit { * @return mean latitude argument.(rad) */ public double getLM() { + initEquinoctial(); return equinoctial.getLM(); } + /** {@inheritDoc} */ + protected PVCoordinates initPVCoordinates() { + // nothing to do here, as the canonical elements are already the cartesian ones + return getPVCoordinates(); + } + /** {@inheritDoc} */ public CartesianOrbit shiftedBy(final double dt) { - return new CartesianOrbit(equinoctial.shiftedBy(dt)); + final PVCoordinates shiftedPV = (getA() < 0) ? shiftPVHyperbolic(dt) : shiftPVElliptic(dt); + return new CartesianOrbit(shiftedPV, getFrame(), getDate().shiftedBy(dt), getMu()); + } + + /** Compute shifted position and velocity in elliptic case. + * @param dt time shift + * @return shifted position and velocity + */ + private PVCoordinates shiftPVElliptic(final double dt) { + + // preliminary computation + final Vector3D pvP = getPVCoordinates().getPosition(); + final Vector3D pvV = getPVCoordinates().getVelocity(); + final double r = pvP.getNorm(); + final double rV2OnMu = r * pvV.getNormSq() / getMu(); + final double a = getA(); + final double eSE = Vector3D.dotProduct(pvP, pvV) / FastMath.sqrt(getMu() * a); + final double eCE = rV2OnMu - 1; + final double e2 = eCE * eCE + eSE * eSE; + + // we can use any arbitrary reference 2D frame in the orbital plane + // in order to simplify some equations below, we use the current position as the u axis + final Vector3D u = pvP.normalize(); + final Vector3D v = Vector3D.crossProduct(getPVCoordinates().getMomentum(), u).normalize(); + + // the following equations rely on the specific choice of u explained above, + // some coefficients that vanish to 0 in this case have already been removed here + final double ex = (eCE - e2) * a / r; + final double ey = -FastMath.sqrt(1 - e2) * eSE * a / r; + final double beta = 1 / (1 + FastMath.sqrt(1 - e2)); + final double thetaE0 = FastMath.atan2(ey + eSE * beta * ex, r / a + ex - eSE * beta * ey); + final double thetaM0 = thetaE0 - ex * FastMath.sin(thetaE0) + ey * FastMath.cos(thetaE0); + + // compute in-plane shifted eccentric argument + final double thetaM1 = thetaM0 + getKeplerianMeanMotion() * dt; + final double thetaE1 = meanToEccentric(thetaM1, ex, ey); + final double cTE = FastMath.cos(thetaE1); + final double sTE = FastMath.sin(thetaE1); + + // compute shifted in-plane cartesian coordinates + final double exey = ex * ey; + final double exCeyS = ex * cTE + ey * sTE; + final double x = a * ((1 - beta * ey * ey) * cTE + beta * exey * sTE - ex); + final double y = a * ((1 - beta * ex * ex) * sTE + beta * exey * cTE - ey); + final double factor = FastMath.sqrt(getMu() / a) / (1 - exCeyS); + final double xDot = factor * (-sTE + beta * ey * exCeyS); + final double yDot = factor * ( cTE - beta * ex * exCeyS); + + return new PVCoordinates(new Vector3D(x, u, y, v), new Vector3D(xDot, u, yDot, v)); + + } + + /** Compute shifted position and velocity in hyperbolic case. + * @param dt time shift + * @return shifted position and velocity + */ + private PVCoordinates shiftPVHyperbolic(final double dt) { + + final PVCoordinates pv = getPVCoordinates(); + final Vector3D pvP = pv.getPosition(); + final Vector3D pvV = pv.getVelocity(); + final Vector3D pvM = pv.getMomentum(); + final double r = pvP.getNorm(); + final double rV2OnMu = r * pvV.getNormSq() / getMu(); + final double a = getA(); + final double muA = getMu() * a; + final double e = FastMath.sqrt(1 - Vector3D.dotProduct(pvM, pvM) / muA); + final double sqrt = FastMath.sqrt((e + 1) / (e - 1)); + + // compute mean anomaly + final double eSH = Vector3D.dotProduct(pvP, pvV) / FastMath.sqrt(-muA); + final double eCH = rV2OnMu - 1; + final double H0 = FastMath.log((eCH + eSH) / (eCH - eSH)) / 2; + final double M0 = e * FastMath.sinh(H0) - H0; + + // find canonical 2D frame with p pointing to perigee + final double v0 = 2 * FastMath.atan(sqrt * FastMath.tanh(H0 / 2)); + final Vector3D p = new Rotation(pvM, -v0).applyTo(pvP).normalize(); + final Vector3D q = Vector3D.crossProduct(pvM, p).normalize(); + + // compute shifted eccentric anomaly + final double M1 = M0 + getKeplerianMeanMotion() * dt; + final double H1 = meanToHyperbolicEccentric(M1, e); + + // compute shifted in-plane cartesian coordinates + final double cH = FastMath.cosh(H1); + final double sH = FastMath.sinh(H1); + final double sE2m1 = FastMath.sqrt((e - 1) * (e + 1)); + + // coordinates of position and velocity in the orbital plane + final double x = a * (cH - e); + final double y = -a * sE2m1 * sH; + final double factor = FastMath.sqrt(getMu() / -a) / (e * cH - 1); + final double xDot = -factor * sH; + final double yDot = factor * sE2m1 * cH; + + return new PVCoordinates(new Vector3D(x, p, y, q), new Vector3D(xDot, p, yDot, q)); + + } + + /** Computes the eccentric in-plane argument from the mean in-plane argument. + * @param thetaM = mean in-plane argument (rad) + * @param ex first component of eccentricity vector + * @param ey second component of eccentricity vector + * @return the eccentric in-plane argument. + */ + private double meanToEccentric(final double thetaM, final double ex, final double ey) { + // Generalization of Kepler equation to in-plane parameters + // with thetaE = eta + E and + // thetaM = eta + M = thetaE - ex.sin(thetaE) + ey.cos(thetaE) + // and eta being counted from an arbitrary reference in the orbital plane + double thetaE = thetaM; + double shift = 0.0; + double thetaEMthetaM = 0.0; + double cosThetaE = FastMath.cos(thetaE); + double sinThetaE = FastMath.sin(thetaE); + int iter = 0; + do { + final double f2 = ex * sinThetaE - ey * cosThetaE; + final double f1 = 1.0 - ex * cosThetaE - ey * sinThetaE; + final double f0 = thetaEMthetaM - f2; + + final double f12 = 2.0 * f1; + shift = f0 * f12 / (f1 * f12 - f0 * f2); + + thetaEMthetaM -= shift; + thetaE = thetaM + thetaEMthetaM; + cosThetaE = FastMath.cos(thetaE); + sinThetaE = FastMath.sin(thetaE); + + } while ((++iter < 50) && (FastMath.abs(shift) > 1.0e-12)); + + return thetaE; + + } + + /** Computes the hyperbolic eccentric anomaly from the mean anomaly. + * <p> + * The algorithm used here for solving hyperbolic Kepler equation is + * a naive initialization and classical Halley method for iterations. + * </p> + * @param M mean anomaly (rad) + * @param e eccentricity + * @return the true anomaly + */ + private double meanToHyperbolicEccentric(final double M, final double e) { + + // resolution of hyperbolic Kepler equation for keplerian parameters + double H = -M; + double shift = 0.0; + double HpM = 0.0; + int iter = 0; + do { + final double f2 = e * FastMath.sinh(H); + final double f1 = e * FastMath.cosh(H) - 1; + final double f0 = f2 - HpM; + + final double f12 = 2 * f1; + shift = f0 * f12 / (f1 * f12 - f0 * f2); + + HpM -= shift; + H = HpM - M; + + } while ((++iter < 50) && (FastMath.abs(shift) > 1.0e-12)); + + return H; + } /** Returns a string representation of this Orbit object. diff --git a/src/main/java/org/orekit/orbits/CircularOrbit.java b/src/main/java/org/orekit/orbits/CircularOrbit.java index 8901690b7f..ce021871e6 100644 --- a/src/main/java/org/orekit/orbits/CircularOrbit.java +++ b/src/main/java/org/orekit/orbits/CircularOrbit.java @@ -78,7 +78,7 @@ public class CircularOrbit public static final int TRUE_LONGITUDE_ARGUMENT = 2; /** Serializable UID. */ - private static final long serialVersionUID = -5031200932453701026L; + private static final long serialVersionUID = 5042463409964008691L; /** Semi-major axis (m). */ private final double a; @@ -133,10 +133,10 @@ public class CircularOrbit switch (type) { case MEAN_LONGITUDE_ARGUMENT : - this.alphaV = computeAlphaM(alpha); + this.alphaV = eccentricToTrue(meanToEccentric(alpha)); break; case ECCENTRIC_LONGITUDE_ARGUMENT : - this.alphaV = computeAlphaE(alpha); + this.alphaV = eccentricToTrue(alpha); break; case TRUE_LONGITUDE_ARGUMENT : this.alphaV = alpha; @@ -171,6 +171,12 @@ public class CircularOrbit final double r = pvP.getNorm(); final double V2 = pvV.getNormSq(); final double rV2OnMu = r * V2 / mu; + + if (rV2OnMu > 2) { + throw OrekitException.createIllegalArgumentException( + OrekitMessages.HYPERBOLIC_ORBIT_NOT_HANDLED_AS, getClass().getName()); + } + a = r / (2 - rV2OnMu); // compute inclination @@ -188,10 +194,11 @@ public class CircularOrbit final double sinRaan = FastMath.sin(raan); final double cosI = FastMath.cos(i); final double sinI = FastMath.sin(i); - final Vector3D rVec = new Vector3D(cosRaan, FastMath.sin(raan), 0); - final Vector3D sVec = new Vector3D(-cosI * sinRaan, cosI * cosRaan, sinI); - final double x2 = Vector3D.dotProduct(pvP, rVec) / a; - final double y2 = Vector3D.dotProduct(pvP, sVec) / a; + final double xP = pvP.getX(); + final double yP = pvP.getY(); + final double zP = pvP.getZ(); + final double x2 = (xP * cosRaan + yP * sinRaan) / a; + final double y2 = ((yP * cosRaan - xP * sinRaan) * cosI + zP * sinI) / a; // compute eccentricity vector final double eSE = Vector3D.dotProduct(pvP, pvV) / FastMath.sqrt(mu * a); @@ -206,7 +213,7 @@ public class CircularOrbit // compute longitude argument final double beta = 1 / (1 + FastMath.sqrt(1 - ex * ex - ey * ey)); - alphaV = computeAlphaE(FastMath.atan2(y2 + ey + eSE * beta * ex, x2 + ex - eSE * beta * ey)); + alphaV = eccentricToTrue(FastMath.atan2(y2 + ey + eSE * beta * ex, x2 + ex - eSE * beta * ey)); } /** Constructor from any kind of orbital parameters. @@ -293,11 +300,11 @@ public class CircularOrbit (epsilon + 1 + ex * cosAlphaV + ey * sinAlphaV)); } - /** Computes the eccentric longitude argument. + /** Computes the true longitude argument from the eccentric longitude argument. * @param alphaE = E + ω eccentric longitude argument (rad) * @return the true longitude argument. */ - private double computeAlphaE(final double alphaE) { + private double eccentricToTrue(final double alphaE) { final double epsilon = FastMath.sqrt(1 - ex * ex - ey * ey); final double cosAlphaE = FastMath.cos(alphaE); final double sinAlphaE = FastMath.sin(alphaE); @@ -313,23 +320,23 @@ public class CircularOrbit return alphaE - ex * FastMath.sin(alphaE) + ey * FastMath.cos(alphaE); } - /** Computes the mean longitude argument. + /** Computes the eccentric longitude argument from the mean longitude argument. * @param alphaM = M + ω mean longitude argument (rad) - * @return the true longitude argument. + * @return the eccentric longitude argument. */ - private double computeAlphaM(final double alphaM) { - // Generalization of Kepler equation to equinoctial parameters + private double meanToEccentric(final double alphaM) { + // Generalization of Kepler equation to circular parameters // with alphaE = PA + E and // alphaM = PA + M = alphaE - ex.sin(alphaE) + ey.cos(alphaE) - double alphaE = alphaM; - double shift = 0.0; + double alphaE = alphaM; + double shift = 0.0; double alphaEMalphaM = 0.0; - double cosLE = FastMath.cos(alphaE); - double sinLE = FastMath.sin(alphaE); - int iter = 0; + double cosAlphaE = FastMath.cos(alphaE); + double sinAlphaE = FastMath.sin(alphaE); + int iter = 0; do { - final double f2 = ex * sinLE - ey * cosLE; - final double f1 = 1.0 - ex * cosLE - ey * sinLE; + final double f2 = ex * sinAlphaE - ey * cosAlphaE; + final double f1 = 1.0 - ex * cosAlphaE - ey * sinAlphaE; final double f0 = alphaEMalphaM - f2; final double f12 = 2.0 * f1; @@ -337,12 +344,12 @@ public class CircularOrbit alphaEMalphaM -= shift; alphaE = alphaM + alphaEMalphaM; - cosLE = FastMath.cos(alphaE); - sinLE = FastMath.sin(alphaE); + cosAlphaE = FastMath.cos(alphaE); + sinAlphaE = FastMath.sin(alphaE); } while ((++iter < 50) && (FastMath.abs(shift) > 1.0e-12)); - return computeAlphaE(alphaE); // which set the alphaV parameter + return alphaE; } @@ -388,6 +395,59 @@ public class CircularOrbit return getAlphaM() + raan; } + /** {@inheritDoc} */ + protected PVCoordinates initPVCoordinates() { + + // get equinoctial parameters + final double equEx = getEquinoctialEx(); + final double equEy = getEquinoctialEy(); + final double hx = getHx(); + final double hy = getHy(); + final double lE = getLE(); + + // inclination-related intermediate parameters + final double hx2 = hx * hx; + final double hy2 = hy * hy; + final double factH = 1. / (1 + hx2 + hy2); + + // reference axes defining the orbital plane + final double ux = (1 + hx2 - hy2) * factH; + final double uy = 2 * hx * hy * factH; + final double uz = -2 * hy * factH; + + final double vx = uy; + final double vy = (1 - hx2 + hy2) * factH; + final double vz = 2 * hx * factH; + + // eccentricity-related intermediate parameters + final double exey = equEx * equEy; + final double ex2 = equEx * equEx; + final double ey2 = equEy * equEy; + final double e2 = ex2 + ey2; + final double eta = 1 + FastMath.sqrt(1 - e2); + final double beta = 1. / eta; + + // eccentric latitude argument + final double cLe = FastMath.cos(lE); + final double sLe = FastMath.sin(lE); + final double exCeyS = equEx * cLe + equEy * sLe; + + // coordinates of position and velocity in the orbital plane + final double x = a * ((1 - beta * ey2) * cLe + beta * exey * sLe - equEx); + final double y = a * ((1 - beta * ex2) * sLe + beta * exey * cLe - equEy); + + final double factor = FastMath.sqrt(getMu() / a) / (1 - exCeyS); + final double xdot = factor * (-sLe + beta * equEy * exCeyS); + final double ydot = factor * ( cLe - beta * equEx * exCeyS); + + final Vector3D position = + new Vector3D(x * ux + y * vx, x * uy + y * vy, x * uz + y * vz); + final Vector3D velocity = + new Vector3D(xdot * ux + ydot * vx, xdot * uy + ydot * vy, xdot * uz + ydot * vz); + return new PVCoordinates(position, velocity); + + } + /** {@inheritDoc} */ public CircularOrbit shiftedBy(final double dt) { return new CircularOrbit(a, ex, ey, i, raan, diff --git a/src/main/java/org/orekit/orbits/EquinoctialOrbit.java b/src/main/java/org/orekit/orbits/EquinoctialOrbit.java index ded75577dc..b2d96c4e20 100644 --- a/src/main/java/org/orekit/orbits/EquinoctialOrbit.java +++ b/src/main/java/org/orekit/orbits/EquinoctialOrbit.java @@ -76,7 +76,7 @@ public class EquinoctialOrbit extends Orbit { public static final int TRUE_LATITUDE_ARGUMENT = 2; /** Serializable UID. */ - private static final long serialVersionUID = -1779638201767656602L; + private static final long serialVersionUID = -2000712440570076839L; /** Semi-major axis (m). */ private final double a; @@ -131,10 +131,10 @@ public class EquinoctialOrbit extends Orbit { switch (type) { case MEAN_LATITUDE_ARGUMENT : - this.lv = computeLM(l); + this.lv = eccentricToTrue(meanToEccentric(l)); break; case ECCENTRIC_LATITUDE_ARGUMENT : - this.lv = computeLE(l); + this.lv = eccentricToTrue(l); break; case TRUE_LATITUDE_ARGUMENT : this.lv = l; @@ -169,7 +169,11 @@ public class EquinoctialOrbit extends Orbit { final double r = pvP.getNorm(); final double V2 = pvV.getNormSq(); final double rV2OnMu = r * V2 / mu; - a = r / (2 - rV2OnMu); + + if (rV2OnMu > 2) { + throw OrekitException.createIllegalArgumentException( + OrekitMessages.HYPERBOLIC_ORBIT_NOT_HANDLED_AS, getClass().getName()); + } // compute inclination vector final Vector3D w = pvCoordinates.getMomentum().normalize(); @@ -178,19 +182,23 @@ public class EquinoctialOrbit extends Orbit { hy = d * w.getX(); // compute true latitude argument - final Vector3D p = pvP; - final double cLv = (p.getX() - d * p.getZ() * w.getX()) / r; - final double sLv = (p.getY() - d * p.getZ() * w.getY()) / r; + final double cLv = (pvP.getX() - d * pvP.getZ() * w.getX()) / r; + final double sLv = (pvP.getY() - d * pvP.getZ() * w.getY()) / r; lv = FastMath.atan2(sLv, cLv); + + // compute semi-major axis + a = r / (2 - rV2OnMu); + // compute eccentricity vector - final double eSE = Vector3D.dotProduct(p, pvV) / FastMath.sqrt(mu * a); + final double eSE = Vector3D.dotProduct(pvP, pvV) / FastMath.sqrt(mu * a); final double eCE = rV2OnMu - 1; final double e2 = eCE * eCE + eSE * eSE; final double f = eCE - e2; final double g = FastMath.sqrt(1 - e2) * eSE; ex = a * (f * cLv + g * sLv) / r; ey = a * (f * sLv - g * cLv) / r; + } /** Constructor from any kind of orbital parameters. @@ -260,16 +268,16 @@ public class EquinoctialOrbit extends Orbit { return lv + 2 * FastMath.atan(num / den); } - /** Computes the eccentric latitude argument. + /** Computes the true latitude argument from the eccentric latitude argument. * @param lE = E + ω + Ω eccentric latitude argument (rad) * @return the true latitude argument */ - private double computeLE(final double lE) { + private double eccentricToTrue(final double lE) { final double epsilon = FastMath.sqrt(1 - ex * ex - ey * ey); final double cosLE = FastMath.cos(lE); final double sinLE = FastMath.sin(lE); - final double num = ex * sinLE - ey * cosLE; - final double den = epsilon + 1 - ex * cosLE - ey * sinLE; + final double num = ex * sinLE - ey * cosLE; + final double den = epsilon + 1 - ex * cosLE - ey * sinLE; return lE + 2 * FastMath.atan(num / den); } @@ -281,11 +289,11 @@ public class EquinoctialOrbit extends Orbit { return lE - ex * FastMath.sin(lE) + ey * FastMath.cos(lE); } - /** Computes the mean latitude argument. + /** Computes the eccentric latitude argument from the mean latitude argument. * @param lM = M + ω + Ω mean latitude argument (rad) - * @return the true latitude argument + * @return the eccentric latitude argument */ - private double computeLM(final double lM) { + private double meanToEccentric(final double lM) { // Generalization of Kepler equation to equinoctial parameters // with lE = PA + RAAN + E and // lM = PA + RAAN + M = lE - ex.sin(lE) + ey.cos(lE) @@ -310,7 +318,7 @@ public class EquinoctialOrbit extends Orbit { } while ((++iter < 50) && (FastMath.abs(shift) > 1.0e-12)); - return computeLE(lE); // which set the lv parameter + return lE; } @@ -328,6 +336,55 @@ public class EquinoctialOrbit extends Orbit { return 2 * FastMath.atan(FastMath.sqrt(hx * hx + hy * hy)); } + /** {@inheritDoc} */ + protected PVCoordinates initPVCoordinates() { + + // get equinoctial parameters + final double lE = getLE(); + + // inclination-related intermediate parameters + final double hx2 = hx * hx; + final double hy2 = hy * hy; + final double factH = 1. / (1 + hx2 + hy2); + + // reference axes defining the orbital plane + final double ux = (1 + hx2 - hy2) * factH; + final double uy = 2 * hx * hy * factH; + final double uz = -2 * hy * factH; + + final double vx = uy; + final double vy = (1 - hx2 + hy2) * factH; + final double vz = 2 * hx * factH; + + // eccentricity-related intermediate parameters + final double exey = ex * ey; + final double ex2 = ex * ex; + final double ey2 = ey * ey; + final double e2 = ex2 + ey2; + final double eta = 1 + FastMath.sqrt(1 - e2); + final double beta = 1. / eta; + + // eccentric latitude argument + final double cLe = FastMath.cos(lE); + final double sLe = FastMath.sin(lE); + final double exCeyS = ex * cLe + ey * sLe; + + // coordinates of position and velocity in the orbital plane + final double x = a * ((1 - beta * ey2) * cLe + beta * exey * sLe - ex); + final double y = a * ((1 - beta * ex2) * sLe + beta * exey * cLe - ey); + + final double factor = FastMath.sqrt(getMu() / a) / (1 - exCeyS); + final double xdot = factor * (-sLe + beta * ey * exCeyS); + final double ydot = factor * ( cLe - beta * ex * exCeyS); + + final Vector3D position = + new Vector3D(x * ux + y * vx, x * uy + y * vy, x * uz + y * vz); + final Vector3D velocity = + new Vector3D(xdot * ux + ydot * vx, xdot * uy + ydot * vy, xdot * uz + ydot * vz); + return new PVCoordinates(position, velocity); + + } + /** {@inheritDoc} */ public EquinoctialOrbit shiftedBy(final double dt) { return new EquinoctialOrbit(a, ex, ey, hx, hy, diff --git a/src/main/java/org/orekit/orbits/KeplerianOrbit.java b/src/main/java/org/orekit/orbits/KeplerianOrbit.java index ddbecdab78..5ea68d580b 100644 --- a/src/main/java/org/orekit/orbits/KeplerianOrbit.java +++ b/src/main/java/org/orekit/orbits/KeplerianOrbit.java @@ -18,6 +18,7 @@ package org.orekit.orbits; import org.apache.commons.math.geometry.Vector3D; import org.apache.commons.math.util.FastMath; +import org.apache.commons.math.util.MathUtils; import org.orekit.errors.OrekitException; import org.orekit.errors.OrekitMessages; import org.orekit.frames.Frame; @@ -42,6 +43,10 @@ import org.orekit.utils.PVCoordinates; * Right Ascension of the Ascending Node and v stands for the true anomaly. * </p> * <p> + * This class supports hyperbolic orbits, using the convention that semi major + * axis is negative for such orbits (and of course eccentricity is greater than 1). + * </p> + * <p> * When orbit is either equatorial or circular, some keplerian elements * (more precisely ω and Ω) become ambiguous so this class should not * be used for such orbits. For this reason, {@link EquinoctialOrbit equinoctial @@ -78,7 +83,21 @@ public class KeplerianOrbit extends Orbit { public static final double E_CIRC = 1.e-10; /** Serializable UID. */ - private static final long serialVersionUID = -8628129146897296527L; + private static final long serialVersionUID = 2077785958734298873L; + + /** First coefficient to compute Kepler equation solver starter. */ + private static final double A; + + /** Second coefficient to compute Kepler equation solver starter. */ + private static final double B; + + static { + final double k1 = 3 * FastMath.PI + 2; + final double k2 = FastMath.PI - 1; + final double k3 = 6 * FastMath.PI - 1; + A = 3 * k2 * k2 / k1; + B = k3 * k3 / (6 * k1); + } /** Semi-major axis (m). */ private final double a; @@ -99,7 +118,7 @@ public class KeplerianOrbit extends Orbit { private final double v; /** Creates a new instance. - * @param a semi-major axis (m) + * @param a semi-major axis (m), negative for hyperbolic orbits * @param e eccentricity * @param i inclination (rad) * @param pa perigee argument (ω, rad) @@ -125,6 +144,11 @@ public class KeplerianOrbit extends Orbit { final Frame frame, final AbsoluteDate date, final double mu) throws IllegalArgumentException { super(frame, date, mu); + + if (a * (1 - e) < 0) { + throw OrekitException.createIllegalArgumentException(OrekitMessages.ORBIT_A_E_MISMATCH_WITH_CONIC_TYPE, a, e); + } + this.a = a; this.e = e; this.i = i; @@ -133,10 +157,14 @@ public class KeplerianOrbit extends Orbit { switch (type) { case MEAN_ANOMALY : - this.v = computeMeanAnomaly(anomaly); + this.v = (a < 0) ? + hyperbolicEccentricToTrue(meanToHyperbolicEccentric(anomaly)) : + ellipticEccentricToTrue(meanToEllipticEccentric(anomaly)); break; case ECCENTRIC_ANOMALY : - this.v = computeEccentricAnomaly(anomaly); + this.v = (a < 0) ? + hyperbolicEccentricToTrue(anomaly) : + ellipticEccentricToTrue(anomaly); break; case TRUE_ANOMALY : this.v = anomaly; @@ -164,20 +192,6 @@ public class KeplerianOrbit extends Orbit { throws IllegalArgumentException { super(pvCoordinates, frame, date, mu); - // compute semi-major axis - final Vector3D pvP = pvCoordinates.getPosition(); - final Vector3D pvV = pvCoordinates.getVelocity(); - final double r = pvP.getNorm(); - final double V2 = pvV.getNormSq(); - final double rV2OnMu = r * V2 / mu; - a = r / (2 - rV2OnMu); - - // compute eccentricity - final double muA = mu * a; - final double eSE = Vector3D.dotProduct(pvP, pvV) / FastMath.sqrt(muA); - final double eCE = rV2OnMu - 1; - e = FastMath.sqrt(eSE * eSE + eCE * eCE); - // compute inclination final Vector3D momentum = pvCoordinates.getMomentum(); final double m2 = Vector3D.dotProduct(momentum, momentum); @@ -189,36 +203,50 @@ public class KeplerianOrbit extends Orbit { // the following comparison with 0 IS REALLY numerically justified and stable raan = (n2 == 0) ? 0 : FastMath.atan2(node.getY(), node.getX()); + // preliminary computations for parameters depending on orbit shape (elliptic or hyperbolic) + final Vector3D pvP = pvCoordinates.getPosition(); + final Vector3D pvV = pvCoordinates.getVelocity(); + final double r = pvP.getNorm(); + final double V2 = pvV.getNormSq(); + final double rV2OnMu = r * V2 / mu; + + // compute semi-major axis (will be negative for hyperbolic orbits) + a = r / (2 - rV2OnMu); + + // compute eccentricity (will be larger than 1 for hyperbolic orbits) + final double muA = mu * a; + e = FastMath.sqrt(1 - m2 / muA); + // compute true anomaly - if (e < E_CIRC) { - v = 0; + if (a > 0) { + if (e < E_CIRC) { + // circular orbit + v = 0; + } else { + // elliptic orbit + final double eSE = Vector3D.dotProduct(pvP, pvV) / FastMath.sqrt(muA); + final double eCE = rV2OnMu - 1; + v = ellipticEccentricToTrue(FastMath.atan2(eSE, eCE)); + } } else { - final double E = FastMath.atan2(eSE, eCE); - final double k = 1 / (1 + FastMath.sqrt(m2 / muA)); - v = E + 2 * FastMath.atan(k * eSE / (1 - k * eCE)); + // hyperbolic orbit + final double eSH = Vector3D.dotProduct(pvP, pvV) / FastMath.sqrt(-muA); + final double eCH = rV2OnMu - 1; + v = hyperbolicEccentricToTrue(FastMath.log((eCH + eSH) / (eCH - eSH)) / 2); } // compute perigee argument - final double cosRaan = FastMath.cos(raan); - final double sinRaan = FastMath.sin(raan); - final double px = cosRaan * pvP.getX() + - sinRaan * pvP.getY(); - final double py = FastMath.cos(i) * (cosRaan * pvP.getY() - sinRaan * pvP.getX()) + - FastMath.sin(i) * pvP.getZ(); + final double px = Vector3D.dotProduct(pvP, node); + final double py = Vector3D.dotProduct(pvP, Vector3D.crossProduct(momentum, node)) / FastMath.sqrt(m2); pa = FastMath.atan2(py, px) - v; + } /** Constructor from any kind of orbital parameters. * @param op orbital parameters to copy */ public KeplerianOrbit(final Orbit op) { - super(op.getFrame(), op.getDate(), op.getMu()); - a = op.getA(); - e = op.getE(); - i = op.getI(); - raan = FastMath.atan2(op.getHy(), op.getHx()); - pa = FastMath.atan2(op.getEquinoctialEy(), op.getEquinoctialEx()) - raan; - v = op.getLv() - (pa + raan); + this(op.getPVCoordinates(), op.getFrame(), op.getDate(), op.getMu()); } /** Get the semi-major axis. @@ -267,52 +295,165 @@ public class KeplerianOrbit extends Orbit { * @return eccentric anomaly (rad) */ public double getEccentricAnomaly() { + if (a < 0) { + // hyperbolic case + final double t = FastMath.sqrt((e - 1) / (e + 1)) * FastMath.tan(v / 2); + return FastMath.log((1 + t) / (1 - t)); + } + + // elliptic case final double beta = e / (1 + FastMath.sqrt((1 - e) * (1 + e))); return v - 2 * FastMath.atan(beta * FastMath.sin(v) / (1 + beta * FastMath.cos(v))); + } - /** Computes the eccentric anomaly. + /** Computes the true anomaly from the elliptic eccentric anomaly. * @param E eccentric anomaly (rad) * @return v the true anomaly */ - private double computeEccentricAnomaly (final double E) { + private double ellipticEccentricToTrue(final double E) { final double beta = e / (1 + FastMath.sqrt((1 - e) * (1 + e))); return E + 2 * FastMath.atan(beta * FastMath.sin(E) / (1 - beta * FastMath.cos(E))); } + /** Computes the true anomaly from the hyperbolic eccentric anomaly. + * @param H hyperbolic eccentric anomaly (rad) + * @return v the true anomaly + */ + private double hyperbolicEccentricToTrue(final double H) { + return 2 * FastMath.atan(FastMath.sqrt((e + 1) / (e - 1)) * FastMath.tanh(H / 2)); + } + /** Get the mean anomaly. * @return mean anomaly (rad) */ public double getMeanAnomaly() { + + if (a < 0) { + // hyperbolic case + final double H = getEccentricAnomaly(); + return e * FastMath.sinh(H) - H; + } + + // elliptic case final double E = getEccentricAnomaly(); return E - e * FastMath.sin(E); + } - /** Computes the mean anomaly. + /** Computes the elliptic eccentric anomaly from the mean anomaly. + * <p> + * The algorithm used here for solving Kepler equation has been published + * in: "Procedures for solving Kepler's Equation", A. W. Odell and + * R. H. Gooding, Celestial Mechanics 38 (1986) 307-334 + * </p> * @param M mean anomaly (rad) * @return v the true anomaly */ - private double computeMeanAnomaly (final double M) { + private double meanToEllipticEccentric(final double M) { + + // reduce M to [-PI PI) interval + final double reducedM = MathUtils.normalizeAngle(M, 0.0); + + // compute start value according to A. W. Odell and R. H. Gooding S12 starter + double E; + if (FastMath.abs(reducedM) < 1.0 / 6.0) { + E = reducedM + e * (FastMath.cbrt(6 * reducedM) - reducedM); + } else { + if (reducedM < 0) { + final double w = FastMath.PI + reducedM; + E = reducedM + e * (A * w / (B - w) - FastMath.PI - reducedM); + } else { + final double w = FastMath.PI - reducedM; + E = reducedM + e * (FastMath.PI - A * w / (B - w) - reducedM); + } + } + + final double e1 = 1 - e; + final boolean noCancellationRisk = (e1 + E * E / 6) >= 0.1; + + // perform two iterations, each consisting of one Halley step and one Newton-Raphson step + for (int j = 0; j < 2; ++j) { + double f; + double fd; + final double fdd = e * FastMath.sin(E); + final double fddd = e * FastMath.cos(E); + if (noCancellationRisk) { + f = (E - fdd) - reducedM; + fd = 1 - fddd; + } else { + f = eMeSinE(E) - reducedM; + final double s = FastMath.sin(0.5 * E); + fd = e1 + 2 * e * s * s; + } + final double dee = f * fd / (0.5 * f * fdd - fd * fd); + + // update eccentric anomaly, using expressions that limit underflow problems + final double w = fd + 0.5 * dee * (fdd + dee * fddd / 3); + fd += dee * (fdd + 0.5 * dee * fddd); + E -= (f - dee * (fd - w)) / fd; + + } - // resolution of Kepler equation for keplerian parameters - double E = M; + // expand the result back to original range + E += M - reducedM; + + return E; + + } + + /** Accurate computation of E - e sin(E). + * <p> + * This method is used when E is close to 0 and e close to 1, + * i.e. near the perigee of almost parabolic orbits + * </p> + * @param E eccentric anomaly + * @return E - e sin(E) + */ + private double eMeSinE(final double E) { + double x = (1 - e) * FastMath.sin(E); + final double mE2 = -E * E; + double term = E; + double d = 0; + // the inequality test below IS intentional and should NOT be replaced by a check with a small tolerance + for (double x0 = Double.NaN; x != x0;) { + d += 2; + term *= mE2 / (d * (d + 1)); + x0 = x; + x = x - term; + } + return x; + } + + /** Computes the hyperbolic eccentric anomaly from the mean anomaly. + * <p> + * The algorithm used here for solving hyperbolic Kepler equation is + * a naive initialization and classical Halley method for iterations. + * </p> + * @param M mean anomaly (rad) + * @return v the true anomaly + */ + private double meanToHyperbolicEccentric(final double M) { + + // resolution of hyperbolic Kepler equation for keplerian parameters + double H = -M; double shift = 0.0; - double EmM = 0.0; - int iter = 0; + double HpM = 0.0; + int iter = 0; do { - final double f2 = e * FastMath.sin(E); - final double f1 = 1.0 - e * FastMath.cos(E); - final double f0 = EmM - f2; + final double f2 = e * FastMath.sinh(H); + final double f1 = e * FastMath.cosh(H) - 1; + final double f0 = f2 - HpM; final double f12 = 2 * f1; shift = f0 * f12 / (f1 * f12 - f0 * f2); - EmM -= shift; - E = M + EmM; + HpM -= shift; + H = HpM - M; } while ((++iter < 50) && (FastMath.abs(shift) > 1.0e-12)); - return computeEccentricAnomaly(E); + return H; } @@ -334,6 +475,10 @@ public class KeplerianOrbit extends Orbit { * @return first component of the inclination vector. */ public double getHx() { + // Check for equatorial retrograde orbit + if (FastMath.abs(i - FastMath.PI) < 1.0e-10) { + return Double.NaN; + } return FastMath.cos(raan) * FastMath.tan(i / 2); } @@ -341,6 +486,10 @@ public class KeplerianOrbit extends Orbit { * @return second component of the inclination vector. */ public double getHy() { + // Check for equatorial retrograde orbit + if (FastMath.abs(i - FastMath.PI) < 1.0e-10) { + return Double.NaN; + } return FastMath.sin(raan) * FastMath.tan(i / 2); } @@ -365,6 +514,79 @@ public class KeplerianOrbit extends Orbit { return pa + raan + getMeanAnomaly(); } + /** {@inheritDoc} */ + protected PVCoordinates initPVCoordinates() { + + // preliminary variables + final double cosRaan = FastMath.cos(raan); + final double sinRaan = FastMath.sin(raan); + final double cosPa = FastMath.cos(pa); + final double sinPa = FastMath.sin(pa); + final double cosI = FastMath.cos(i); + final double sinI = FastMath.sin(i); + + final double crcp = cosRaan * cosPa; + final double crsp = cosRaan * sinPa; + final double srcp = sinRaan * cosPa; + final double srsp = sinRaan * sinPa; + + // reference axes defining the orbital plane + final Vector3D p = new Vector3D( crcp - cosI * srsp, srcp + cosI * crsp, sinI * sinPa); + final Vector3D q = new Vector3D(-crsp - cosI * srcp, -srsp + cosI * crcp, sinI * cosPa); + + return (a > 0) ? initPVCoordinatesElliptical(p, q) : initPVCoordinatesHyperbolic(p, q); + + } + + /** Initialize the position/velocity coordinates, elliptic case. + * @param p unit vector in the orbital plane pointing towards perigee + * @param q unit vector in the orbital plane in quadrature with q + * @return computed position/velocity coordinates + */ + private PVCoordinates initPVCoordinatesElliptical(final Vector3D p, final Vector3D q) { + + // elliptic eccentric anomaly + final double uME2 = (1 - e) * (1 + e); + final double s1Me2 = FastMath.sqrt(uME2); + final double E = getEccentricAnomaly(); + final double cosE = FastMath.cos(E); + final double sinE = FastMath.sin(E); + + // coordinates of position and velocity in the orbital plane + final double x = a * (cosE - e); + final double y = a * sinE * s1Me2; + final double factor = FastMath.sqrt(getMu() / a) / (1 - e * cosE); + final double xDot = -sinE * factor; + final double yDot = cosE * s1Me2 * factor; + + return new PVCoordinates(new Vector3D(x, p, y, q), new Vector3D(xDot, p, yDot, q)); + + } + + /** Initialize the position/velocity coordinates, hyperbolic case. + * @param p unit vector in the orbital plane pointing towards perigee + * @param q unit vector in the orbital plane in quadrature with q + * @return computed position/velocity coordinates + */ + private PVCoordinates initPVCoordinatesHyperbolic(final Vector3D p, final Vector3D q) { + + // hyperbolic eccentric anomaly + final double h = getEccentricAnomaly(); + final double cH = FastMath.cosh(h); + final double sH = FastMath.sinh(h); + final double sE2m1 = FastMath.sqrt((e - 1) * (e + 1)); + + // coordinates of position and velocity in the orbital plane + final double x = a * (cH - e); + final double y = -a * sE2m1 * sH; + final double factor = FastMath.sqrt(getMu() / -a) / (e * cH - 1); + final double xDot = -factor * sH; + final double yDot = factor * sE2m1 * cH; + + return new PVCoordinates(new Vector3D(x, p, y, q), new Vector3D(xDot, p, yDot, q)); + + } + /** {@inheritDoc} */ public KeplerianOrbit shiftedBy(final double dt) { return new KeplerianOrbit(a, e, i, pa, raan, diff --git a/src/main/java/org/orekit/orbits/Orbit.java b/src/main/java/org/orekit/orbits/Orbit.java index 8583335706..d20563b70a 100644 --- a/src/main/java/org/orekit/orbits/Orbit.java +++ b/src/main/java/org/orekit/orbits/Orbit.java @@ -18,9 +18,7 @@ package org.orekit.orbits; import java.io.Serializable; -import org.apache.commons.math.geometry.Vector3D; import org.apache.commons.math.util.FastMath; -import org.apache.commons.math.util.MathUtils; import org.orekit.errors.OrekitException; import org.orekit.errors.OrekitMessages; import org.orekit.frames.Frame; @@ -63,7 +61,7 @@ import org.orekit.utils.PVCoordinates; public abstract class Orbit implements TimeStamped, Serializable { /** Serializable UID. */ - private static final long serialVersionUID = -5391613859481554640L; + private static final long serialVersionUID = 438733454597999578L; /** Frame in which are defined the orbital parameters. */ private final Frame frame; @@ -136,6 +134,7 @@ public abstract class Orbit implements TimeStamped, Serializable { } /** Get the semi-major axis. + * <p>Note that the semi-major axis is considered negative for hyperbolic orbits.</p> * @return semi-major axis (m) */ public abstract double getA(); @@ -197,11 +196,11 @@ public abstract class Orbit implements TimeStamped, Serializable { /** Get the keplerian period. * <p>The keplerian period is computed directly from semi major axis * and central acceleration constant.</p> - * @return keplerian period in seconds + * @return keplerian period in seconds, or positive infinity for hyperbolic orbits */ public double getKeplerianPeriod() { final double a = getA(); - return MathUtils.TWO_PI * a * FastMath.sqrt(a / mu); + return (a < 0) ? Double.POSITIVE_INFINITY : 2.0 * FastMath.PI * a * FastMath.sqrt(a / mu); } /** Get the keplerian mean motion. @@ -210,8 +209,8 @@ public abstract class Orbit implements TimeStamped, Serializable { * @return keplerian mean motion in radians per second */ public double getKeplerianMeanMotion() { - final double a = getA(); - return FastMath.sqrt(mu / a) / a; + final double absA = FastMath.abs(getA()); + return FastMath.sqrt(mu / absA) / absA; } /** Get the date of orbital parameters. @@ -221,7 +220,7 @@ public abstract class Orbit implements TimeStamped, Serializable { return date; } - /** Get the {@link PVCoordinates}. + /** Get the {@link PVCoordinates} in a specified frame. * @param outputFrame frame in which the position/velocity coordinates shall be computed * @return pvCoordinates in the specified output frame * @exception OrekitException if transformation between frames cannot be computed @@ -230,7 +229,7 @@ public abstract class Orbit implements TimeStamped, Serializable { public PVCoordinates getPVCoordinates(final Frame outputFrame) throws OrekitException { if (pvCoordinates == null) { - initPVCoordinates(); + pvCoordinates = initPVCoordinates(); } // If output frame requested is the same as definition frame, @@ -250,65 +249,15 @@ public abstract class Orbit implements TimeStamped, Serializable { */ public PVCoordinates getPVCoordinates() { if (pvCoordinates == null) { - initPVCoordinates(); + pvCoordinates = initPVCoordinates(); } return pvCoordinates; } - /** Initialize the position/velocity coordinates. + /** Compute the position/velocity coordinates from the canonical parameters. + * @return computed position/velocity coordinates */ - private void initPVCoordinates() { - - // get equinoctial parameters - final double a = getA(); - final double ex = getEquinoctialEx(); - final double ey = getEquinoctialEy(); - final double hx = getHx(); - final double hy = getHy(); - final double lE = getLE(); - - // inclination-related intermediate parameters - final double hx2 = hx * hx; - final double hy2 = hy * hy; - final double factH = 1. / (1 + hx2 + hy2); - - // reference axes defining the orbital plane - final double ux = (1 + hx2 - hy2) * factH; - final double uy = 2 * hx * hy * factH; - final double uz = -2 * hy * factH; - - final double vx = uy; - final double vy = (1 - hx2 + hy2) * factH; - final double vz = 2 * hx * factH; - - // eccentricity-related intermediate parameters - final double exey = ex * ey; - final double ex2 = ex * ex; - final double ey2 = ey * ey; - final double e2 = ex2 + ey2; - final double eta = 1 + FastMath.sqrt(1 - e2); - final double beta = 1. / eta; - - // eccentric latitude argument - final double cLe = FastMath.cos(lE); - final double sLe = FastMath.sin(lE); - final double exCeyS = ex * cLe + ey * sLe; - - // coordinates of position and velocity in the orbital plane - final double x = a * ((1 - beta * ey2) * cLe + beta * exey * sLe - ex); - final double y = a * ((1 - beta * ex2) * sLe + beta * exey * cLe - ey); - - final double factor = FastMath.sqrt(mu / a) / (1 - exCeyS); - final double xdot = factor * (-sLe + beta * ey * exCeyS); - final double ydot = factor * ( cLe - beta * ex * exCeyS); - - final Vector3D position = - new Vector3D(x * ux + y * vx, x * uy + y * vy, x * uz + y * vz); - final Vector3D velocity = - new Vector3D(xdot * ux + ydot * vx, xdot * uy + ydot * vy, xdot * uz + ydot * vz); - pvCoordinates = new PVCoordinates(position, velocity); - - } + protected abstract PVCoordinates initPVCoordinates(); /** Get a time-shifted orbit. * <p> diff --git a/src/main/java/org/orekit/propagation/SpacecraftState.java b/src/main/java/org/orekit/propagation/SpacecraftState.java index 29b1d823f8..3af432e63c 100644 --- a/src/main/java/org/orekit/propagation/SpacecraftState.java +++ b/src/main/java/org/orekit/propagation/SpacecraftState.java @@ -160,9 +160,9 @@ public class SpacecraftState implements TimeStamped, Serializable { * that these results may be different for other orbits. * </p> * <table border="1" cellpadding="5"> - * <tr bgcolor="#ccccff"><font size="+3"><th>interpolation time (s)</th> + * <tr bgcolor="#ccccff"><th>interpolation time (s)</th> * <th>position error (m)</th><th>velocity error (m/s)</th> - * <th>attitude error (°)</th></font></tr> + * <th>attitude error (°)</th></tr> * <tr><td bgcolor="#eeeeff"> 60</td><td> 20</td><td>1</td><td>0.001</td></tr> * <tr><td bgcolor="#eeeeff">120</td><td> 100</td><td>2</td><td>0.002</td></tr> * <tr><td bgcolor="#eeeeff">300</td><td> 600</td><td>4</td><td>0.005</td></tr> diff --git a/src/main/java/org/orekit/propagation/events/AdaptedEventDetector.java b/src/main/java/org/orekit/propagation/events/AdaptedEventDetector.java index da28c71409..79454eaf82 100644 --- a/src/main/java/org/orekit/propagation/events/AdaptedEventDetector.java +++ b/src/main/java/org/orekit/propagation/events/AdaptedEventDetector.java @@ -18,11 +18,10 @@ package org.orekit.propagation.events; import org.apache.commons.math.ode.events.EventException; import org.apache.commons.math.ode.events.EventHandler; -import org.orekit.attitudes.AttitudeLaw; import org.orekit.errors.OrekitException; import org.orekit.frames.Frame; -import org.orekit.orbits.EquinoctialOrbit; import org.orekit.propagation.SpacecraftState; +import org.orekit.propagation.numerical.StateMapper; import org.orekit.time.AbsoluteDate; /** Adapt an {@link org.orekit.propagation.events.EventDetector} @@ -35,6 +34,9 @@ public class AdaptedEventDetector implements EventHandler { /** Serializable UID. */ private static final long serialVersionUID = -2156830611432730429L; + /** Mapper between spacecraft state and simple array. */ + private StateMapper mapper; + /** Underlying event detector. */ private final EventDetector detector; @@ -47,31 +49,29 @@ public class AdaptedEventDetector implements EventHandler { /** integrationFrame frame in which integration is performed. */ private final Frame integrationFrame; - /** attitudeLaw spacecraft attitude law. */ - private final AttitudeLaw attitudeLaw; - /** Build a wrapped event detector. * @param detector event detector to wrap + * @param mapper mapper between spacecraft state and simple array * @param referenceDate reference date from which t is counted * @param mu central body attraction coefficient (m<sup>3</sup>/s<sup>2</sup>) * @param integrationFrame frame in which integration is performed - * @param attitudeLaw spacecraft attitude law */ - public AdaptedEventDetector(final EventDetector detector, + public AdaptedEventDetector(final EventDetector detector, final StateMapper mapper, final AbsoluteDate referenceDate, final double mu, - final Frame integrationFrame, final AttitudeLaw attitudeLaw) { + final Frame integrationFrame) { this.detector = detector; + this.mapper = mapper; this.referenceDate = referenceDate; this.mu = mu; this.integrationFrame = integrationFrame; - this.attitudeLaw = attitudeLaw; } /** {@inheritDoc} */ public double g(final double t, final double[] y) throws EventException { try { - return detector.g(mapState(t, y)); + final AbsoluteDate currentDate = referenceDate.shiftedBy(t); + return detector.g(mapper.mapArrayToState(y, currentDate, mu, integrationFrame)); } catch (OrekitException oe) { throw new EventException(oe); } @@ -81,7 +81,10 @@ public class AdaptedEventDetector implements EventHandler { public int eventOccurred(final double t, final double[] y, final boolean increasing) throws EventException { try { - final int whatNext = detector.eventOccurred(mapState(t, y), increasing); + final AbsoluteDate currentDate = referenceDate.shiftedBy(t); + final int whatNext = detector.eventOccurred(mapper.mapArrayToState(y, currentDate, mu, + integrationFrame), + increasing); switch (whatNext) { case EventDetector.STOP : return STOP; @@ -101,7 +104,9 @@ public class AdaptedEventDetector implements EventHandler { public void resetState(final double t, final double[] y) throws EventException { try { - final SpacecraftState newState = detector.resetState(mapState(t, y)); + final AbsoluteDate currentDate = referenceDate.shiftedBy(t); + final SpacecraftState newState = detector.resetState(mapper.mapArrayToState(y, currentDate, mu, + integrationFrame)); y[0] = newState.getA(); y[1] = newState.getEquinoctialEx(); y[2] = newState.getEquinoctialEy(); @@ -114,26 +119,4 @@ public class AdaptedEventDetector implements EventHandler { } } - /** Convert state array to space dynamics objects - * ({@link org.orekit.time.AbsoluteDate AbsoluteDate} and - * ({@link org.orekit.orbits.Orbit OrbitalParameters}). - * @param t integration time (s) - * @param y state as a flat array - * @return state corresponding to the flat array as a space dynamics object - * @exception OrekitException if attitude law cannot provide state - */ - private SpacecraftState mapState(final double t, final double [] y) - throws OrekitException { - - // update space dynamics view - final AbsoluteDate currentDate = referenceDate.shiftedBy(t); - final EquinoctialOrbit currentOrbit = - new EquinoctialOrbit(y[0], y[1], y[2], y[3], y[4], y[5], - EquinoctialOrbit.TRUE_LATITUDE_ARGUMENT, - integrationFrame, currentDate, mu); - - return new SpacecraftState(currentOrbit, attitudeLaw.getAttitude(currentOrbit), y[6]); - - } - } diff --git a/src/main/java/org/orekit/propagation/events/EventState.java b/src/main/java/org/orekit/propagation/events/EventState.java index cd2b41a522..7bea078535 100644 --- a/src/main/java/org/orekit/propagation/events/EventState.java +++ b/src/main/java/org/orekit/propagation/events/EventState.java @@ -148,7 +148,9 @@ class EventState implements Serializable { for (int i = 0; i < n; ++i) { // evaluate detector value at the end of the substep - final AbsoluteDate tb = (i == n - 1) ? t1 : start.shiftedBy((i + 1) * h); + // TODO this may lead to infinite loops + // final AbsoluteDate tb = (i == n - 1) ? t1 : start.shiftedBy((i + 1) * h); + final AbsoluteDate tb = start.shiftedBy((i + 1) * h); interpolator.setInterpolatedDate(tb); final double gb = detector.g(interpolator.getInterpolatedState()); @@ -195,7 +197,7 @@ class EventState implements Serializable { } } - final double dtRoot = (dtA <= dtB) ? solver.solve(f, dtA, dtB) : solver.solve(f, dtB, dtA); + final double dtRoot = (dtA <= dtB) ? solver.solve(1000, f, dtA, dtB) : solver.solve(1000, f, dtB, dtA); final AbsoluteDate root = t0.shiftedBy(dtRoot); if ((previousEventTime != null) && diff --git a/src/main/java/org/orekit/propagation/events/GroundMaskElevationDetector.java b/src/main/java/org/orekit/propagation/events/GroundMaskElevationDetector.java index 744325ea6e..e41a5b8bec 100644 --- a/src/main/java/org/orekit/propagation/events/GroundMaskElevationDetector.java +++ b/src/main/java/org/orekit/propagation/events/GroundMaskElevationDetector.java @@ -38,14 +38,14 @@ import org.orekit.propagation.SpacecraftState; * azimuth values, second row with elevation values, as in the following snippet: * <pre> * double [][] mask = { - * {FastMathMath.toRadians(0), FastMath.toRadians(10)}, - * {FastMathMath.toRadians(45), FastMath.toRadians(8)}, - * {FastMathMath.toRadians(90), FastMath.toRadians(6)}, - * {FastMathMath.toRadians(135), FastMath.toRadians(4)}, - * {FastMathMath.toRadians(180), FastMath.toRadians(5)}, - * {FastMathMath.toRadians(225), FastMath.toRadians(6)}, - * {FastMathMath.toRadians(270), FastMath.toRadians(8)}, - * {FastMathMath.toRadians(315), FastMath.toRadians(9)} + * {FastMathFastMath.toRadians(0), FastMath.toRadians(10)}, + * {FastMathFastMath.toRadians(45), FastMath.toRadians(8)}, + * {FastMathFastMath.toRadians(90), FastMath.toRadians(6)}, + * {FastMathFastMath.toRadians(135), FastMath.toRadians(4)}, + * {FastMathFastMath.toRadians(180), FastMath.toRadians(5)}, + * {FastMathFastMath.toRadians(225), FastMath.toRadians(6)}, + * {FastMathFastMath.toRadians(270), FastMath.toRadians(8)}, + * {FastMathFastMath.toRadians(315), FastMath.toRadians(9)} * }; * </pre> * </p> diff --git a/src/main/java/org/orekit/propagation/numerical/AccelerationJacobiansProvider.java b/src/main/java/org/orekit/propagation/numerical/AccelerationJacobiansProvider.java new file mode 100644 index 0000000000..bdcf843564 --- /dev/null +++ b/src/main/java/org/orekit/propagation/numerical/AccelerationJacobiansProvider.java @@ -0,0 +1,51 @@ +/* Copyright 2010 Centre National d'Études Spatiales + * Licensed to CS Communication & Systèmes (CS) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * CS licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.orekit.propagation.numerical; + +import org.orekit.errors.OrekitException; +import org.orekit.forces.Parameterizable; +import org.orekit.propagation.SpacecraftState; + +/** + * Interface for computing acceleration jacobians, for the sake of {@link PartialDerivativesEquations + * partial derivatives equations}. + * @author Luc Maisonobe + * @version $Revision$ $Date$ + */ +public interface AccelerationJacobiansProvider extends Parameterizable { + + /** Compute acceleration derivatives with respect to state parameters. + * @param s spacecraft state + * @param dAccdPos acceleration derivatives with respect to position + * @param dAccdVel acceleration derivatives with respect to velocity + * @param dAccdM acceleration derivatives with respect to mass (may be null when + * the caller does not need the derivatives with respect to mass) + * @exception OrekitException if derivatives cannot be computed + */ + void addDAccDState(SpacecraftState s, double[][] dAccdPos, double[][] dAccdVel, double[] dAccdM) + throws OrekitException; + + /** Compute acceleration derivatives with respect to additional parameters. + * @param s spacecraft state + * @param paramName name of the parameter with respect to which derivatives are required + * @param dAccdParam acceleration derivatives with respect to specified parameters + * @exception OrekitException if derivatives cannot be computed + */ + void addDAccDParam(SpacecraftState s, String paramName, double[] dAccdParam) + throws OrekitException; + +} diff --git a/src/main/java/org/orekit/propagation/numerical/AdditionalEquations.java b/src/main/java/org/orekit/propagation/numerical/AdditionalEquations.java new file mode 100644 index 0000000000..99fbfe064a --- /dev/null +++ b/src/main/java/org/orekit/propagation/numerical/AdditionalEquations.java @@ -0,0 +1,71 @@ +/* Copyright 2010 Centre National d'Études Spatiales + * Licensed to CS Communication & Systèmes (CS) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * CS licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.orekit.propagation.numerical; + +import java.io.Serializable; + +import org.orekit.errors.OrekitException; +import org.orekit.propagation.SpacecraftState; + + +/** This interface allows users to add their own differential equations to a numerical propagator. + * + * <p> + * In some cases users may need to integrate some problem-specific equations along with + * classical spacecraft equations of motions. One example is optimal control in low + * thrust where adjoint parameters linked to the minimized hamiltonian must be integrated. + * Another example is formation flying or rendez-vous which use the Clohessy-Whiltshire + * equations for the relative motion. + * </p> + * <p> + * This interface allows users to add such equations to a {@link NumericalPropagator numerical + * propagator}. Users provide the equations as an implementation of this interface and register + * it to the propagator thanks to its {@link + * NumericalPropagator#addAdditionalEquations(AdditionalEquations)} method. Several such objects + * can be registered with each numerical propagator, but it is recommended to gather in the same + * object the sets of parameters which equations can interact on each others states. + * </p> + * <p> + * The additional parameters are gathered in a simple p array. The additional equations compute + * the pDot array, which is the time-derivative of the p array. Since the additional parameters + * p may also have an influence on the equations of motion themselves (for example an equation + * linked to a complex thrust model may induce an acceleration and a mass change), the same + * {@link TimeDerivativesEquations time derivatives equations adder} already shared by all force + * models to add their contributions is also provided to the additional equations implementation + * object. This means these equations can be used as an additional force model if needed. If the + * additional parameters have no influence at all on the spacecraft state, this adder can + * simply be ignored. + * </p> + * @see NumericalPropagator + * @author Luc Maisonobe + * @version $Revision$ $Date$ + */ +public interface AdditionalEquations extends Serializable { + + /** Compute the derivatives related to the additional parameters. + * @param s current state information: date, kinematics, attitude + * @param adder object where the contribution of the additional parameters + * p to the orbit evolution (accelerations, mass time-derivative) should be added + * @param p current value of the additional parameters + * @param pDot placeholder where the derivatives of the additional parameters + * should be put + * @exception OrekitException if some specific error occurs + */ + void computeDerivatives(SpacecraftState s, TimeDerivativesEquations adder, + double[] p, double[] pDot) throws OrekitException; + +} diff --git a/src/main/java/org/orekit/propagation/numerical/AdditionalStateAndEquations.java b/src/main/java/org/orekit/propagation/numerical/AdditionalStateAndEquations.java new file mode 100644 index 0000000000..e9ebcf578b --- /dev/null +++ b/src/main/java/org/orekit/propagation/numerical/AdditionalStateAndEquations.java @@ -0,0 +1,82 @@ +/* Copyright 2010 Centre National d'Études Spatiales + * Licensed to CS Communication & Systèmes (CS) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * CS licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.orekit.propagation.numerical; + + +/** This class is a container for additional state parameters and their associated evolution equation. +* +* <p> +* This object is a container allowing the propagator to keep constant consistency between additional +* states and the corresponding equations. It allows to set additional state values, get current +* additional state value and by reference on the associated additional equations. +* </p> +* @see NumericalPropagator +* @see AdditionalEquations +* @author Luc Maisonobe +* @version $Revision: 3409 $ $Date: 2010-06-10 15:42:04 +0200 (jeu., 10 juin 2010) $ +*/ +public class AdditionalStateAndEquations { + + /** Additional equations. */ + private AdditionalEquations addEquations; + + /** Current additional state. */ + private double[] addState; + + /** Current additional state derivatives. */ + private double[] addStateDot; + + /** Create a new instance of AdditionalStateAndEquations, based on additional equations definition. + * @param addEqu additional equations. + */ + public AdditionalStateAndEquations(final AdditionalEquations addEqu) { + this.addEquations = addEqu; + } + + /** Get a reference to the current value of the additional state. + * <p>The array returned is a true reference to the state array, so it may be used + * to store data into it.</> + * @return a reference current value of the addditional state. + */ + public double[] getAdditionalState() { + return addState; + } + + /** Get a reference to the current value of the additional state derivatives. + * <p>The array returned is a true reference to the state array, so it may be used + * to store data into it.</> + * @return a reference current value of the addditional state derivatives. + */ + public double[] getAdditionalStateDot() { + return addStateDot; + } + + /** Gets the instance of additional equations. + * @return current value of the additional equations. + */ + public AdditionalEquations getAdditionalEquations() { + return addEquations; + } + + /** Sets a value to additional state. + * @param state additional state value. + */ + public void setAdditionalState(final double[] state) { + this.addState = state.clone(); + this.addStateDot = new double[state.length]; + } +} diff --git a/src/main/java/org/orekit/propagation/numerical/Jacobianizer.java b/src/main/java/org/orekit/propagation/numerical/Jacobianizer.java new file mode 100644 index 0000000000..0706c580b6 --- /dev/null +++ b/src/main/java/org/orekit/propagation/numerical/Jacobianizer.java @@ -0,0 +1,282 @@ +/* Copyright 2010 Centre National d'Études Spatiales + * Licensed to CS Communication & Systèmes (CS) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * CS licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.orekit.propagation.numerical; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.math.geometry.Vector3D; +import org.apache.commons.math.util.FastMath; +import org.apache.commons.math.util.MathUtils; +import org.orekit.errors.OrekitException; +import org.orekit.forces.ForceModel; +import org.orekit.frames.Frame; +import org.orekit.frames.Transform; +import org.orekit.orbits.CartesianOrbit; +import org.orekit.orbits.Orbit; +import org.orekit.propagation.SpacecraftState; +import org.orekit.utils.PVCoordinates; + +/** Class enabling basic {@link ForceModel} instances + * to be used when processing spacecraft state partial derivatives. + * @author Véronique Pommier-Maurussane + * @version $Revision$ $Date$ + */ +class Jacobianizer implements AccelerationJacobiansProvider { + + /** Serializable UID. */ + private static final long serialVersionUID = -352915943197943037L; + + /** Wrapped force model instance. */ + private final ForceModel forceModel; + + /** Step used for finite difference computation with respect to spacecraft position. */ + private double hPos; + + /** Step used for finite difference computation with respect to spacecraft velocity. */ + private double hVel; + + /** Step used for finite difference computation with respect to spacecraft mass. */ + private double hMass; + + /** Step used for finite difference computation with respect to parameters value. */ + private final Map<String, Double> hParam; + + /** Dedicated adder used to retrieve nominal acceleration. */ + private final AccelerationRetriever nominal; + + /** Dedicated adder used to retrieve shifted acceleration. */ + private final AccelerationRetriever shifted; + + /** Simple constructor. + * @param forceModel force model instance to wrap + * @param paramsAndSteps collection of parameters and their associated steps + * @param hPos step used for finite difference computation with respect to spacecraft position (m) + * @param hVel step used for finite difference computation with respect to spacecraft velocity (m/s) + * @param hMass step used for finite difference computation with respect to spacecraft mass (kg) + */ + public Jacobianizer(final ForceModel forceModel, final Collection<ParameterConfiguration> paramsAndSteps, + final double hPos, final double hVel, final double hMass) { + + this.forceModel = forceModel; + this.hParam = new HashMap<String, Double>(); + this.hPos = hPos; + this.hVel = hVel; + this.hMass = hMass; + this.nominal = new AccelerationRetriever(); + this.shifted = new AccelerationRetriever(); + + // set up parameters for jacobian computation + for (final ParameterConfiguration param : paramsAndSteps) { + final String name = param.getParameterName(); + if (forceModel.isSupported(name)) { + double step = param.getHP(); + if (Double.isNaN(step)) { + step = forceModel.getParameter(name) * FastMath.sqrt(MathUtils.EPSILON); + } + hParam.put(name, step); + } + } + + } + + /** Compute acceleration. + * @param retriever acceleration retriever to use for storing acceleration + * @param s original state + * @param p shifted position + * @param v shifted velocity + * @param m shifted mass + * @exception OrekitException if the underlying force models cannot compute the acceleration + */ + private void computeShiftedAcceleration(final AccelerationRetriever retriever, final SpacecraftState s, + final Vector3D p, final Vector3D v, final double m) + throws OrekitException { + final Orbit shiftedORbit = new CartesianOrbit(new PVCoordinates(p, v), s.getFrame(), s.getDate(), s.getMu()); + retriever.initDerivatives(null, shiftedORbit); + forceModel.addContribution(new SpacecraftState(shiftedORbit, s.getAttitude(), m), retriever); + } + + /** {@inheritDoc} */ + public void addDAccDState(final SpacecraftState s, + final double[][] dAccdPos, final double[][] dAccdVel, final double[] dAccdM) + throws OrekitException { + + // compute df/dy where f is the ODE and y is the state array + + final Vector3D p0 = s.getPVCoordinates().getPosition(); + final Vector3D v0 = s.getPVCoordinates().getVelocity(); + final double m0 = s.getMass(); + computeShiftedAcceleration(nominal, s, p0, v0, m0); + + // jacobian with respect to position + computeShiftedAcceleration(shifted, s, new Vector3D(p0.getX() + hPos, p0.getY(), p0.getZ()), v0, m0); + dAccdPos[0][0] += (shifted.getX() - nominal.getX()) / hPos; + dAccdPos[1][0] += (shifted.getY() - nominal.getY()) / hPos; + dAccdPos[2][0] += (shifted.getZ() - nominal.getZ()) / hPos; + + computeShiftedAcceleration(shifted, s, new Vector3D(p0.getX(), p0.getY() + hPos, p0.getZ()), v0, m0); + dAccdPos[0][1] += (shifted.getX() - nominal.getX()) / hPos; + dAccdPos[1][1] += (shifted.getY() - nominal.getY()) / hPos; + dAccdPos[2][1] += (shifted.getZ() - nominal.getZ()) / hPos; + + computeShiftedAcceleration(shifted, s, new Vector3D(p0.getX(), p0.getY(), p0.getZ() + hPos), v0, m0); + dAccdPos[0][2] += (shifted.getX() - nominal.getX()) / hPos; + dAccdPos[1][2] += (shifted.getY() - nominal.getY()) / hPos; + dAccdPos[2][2] += (shifted.getZ() - nominal.getZ()) / hPos; + + // jacobian with respect to velocity + computeShiftedAcceleration(shifted, s, p0, new Vector3D(v0.getX() + hVel, v0.getY(), v0.getZ()), m0); + dAccdVel[0][0] += (shifted.getX() - nominal.getX()) / hPos; + dAccdVel[1][0] += (shifted.getY() - nominal.getY()) / hPos; + dAccdVel[2][0] += (shifted.getZ() - nominal.getZ()) / hPos; + + computeShiftedAcceleration(shifted, s, p0, new Vector3D(v0.getX(), v0.getY() + hVel, v0.getZ()), m0); + dAccdVel[0][1] += (shifted.getX() - nominal.getX()) / hPos; + dAccdVel[1][1] += (shifted.getY() - nominal.getY()) / hPos; + dAccdVel[2][1] += (shifted.getZ() - nominal.getZ()) / hPos; + + computeShiftedAcceleration(shifted, s, p0, new Vector3D(v0.getX(), v0.getY(), v0.getZ() + hVel), m0); + dAccdVel[0][2] += (shifted.getX() - nominal.getX()) / hPos; + dAccdVel[1][2] += (shifted.getY() - nominal.getY()) / hPos; + dAccdVel[2][2] += (shifted.getZ() - nominal.getZ()) / hPos; + + if (dAccdM != null) { + // jacobian with respect to mass + computeShiftedAcceleration(shifted, s, p0, v0, m0 + hMass); + dAccdM[0] += (shifted.getX() - nominal.getX()) / hMass; + dAccdM[1] += (shifted.getY() - nominal.getY()) / hMass; + dAccdM[2] += (shifted.getZ() - nominal.getZ()) / hMass; + } + + + } + + /** {@inheritDoc} */ + public void addDAccDParam(final SpacecraftState s, final String paramName, + final double[] dAccdParam) throws OrekitException { + final double hP = hParam.get(paramName); + nominal.initDerivatives(null, s.getOrbit()); + forceModel.addContribution(s, nominal); + + final double paramValue = forceModel.getParameter(paramName); + forceModel.setParameter(paramName, paramValue + hP); + shifted.initDerivatives(null, s.getOrbit()); + forceModel.addContribution(s, shifted); + forceModel.setParameter(paramName, paramValue); + + dAccdParam[0] += (shifted.getX() - nominal.getX()) / hP; + dAccdParam[1] += (shifted.getY() - nominal.getY()) / hP; + dAccdParam[2] += (shifted.getZ() - nominal.getZ()) / hP; + } + + /** {@inheritDoc} */ + public double getParameter(final String name) throws IllegalArgumentException { + return forceModel.getParameter(name); + } + + /** {@inheritDoc} */ + public Collection<String> getParametersNames() { + return forceModel.getParametersNames(); + } + + /** {@inheritDoc} */ + public boolean isSupported(final String name) { + return forceModel.isSupported(name); + } + + /** {@inheritDoc} */ + public void setParameter(final String name, final double value) throws IllegalArgumentException { + forceModel.setParameter(name, value); + } + + /** Internal class for retrieving accelerations. */ + private static class AccelerationRetriever extends TimeDerivativesEquations { + + /** Serializable UID. */ + private static final long serialVersionUID = -2794923839784176080L; + + /** Stored acceleration. */ + private final double[] acceleration; + + /** Current orbit. */ + private Orbit orbit; + + /** Simple constructor. + */ + protected AccelerationRetriever() { + acceleration = new double[3]; + this.orbit = null; + } + + /** Get X component of acceleration. + * @return X component of acceleration + */ + public double getX() { + return acceleration[0]; + } + + /** Get Y component of acceleration. + * @return Y component of acceleration + */ + public double getY() { + return acceleration[1]; + } + + /** Get Z component of acceleration. + * @return Z component of acceleration + */ + public double getZ() { + return acceleration[2]; + } + + /** {@inheritDoc} */ + void initDerivatives(final double[] yDot, final Orbit currentOrbit) { + acceleration[0] = 0; + acceleration[1] = 0; + acceleration[2] = 0; + this.orbit = currentOrbit; + } + + /** {@inheritDoc} */ + public void addKeplerContribution(final double mu) { + final Vector3D position = orbit.getPVCoordinates().getPosition(); + final double r2 = position.getNormSq(); + final double coeff = -mu / (r2 * FastMath.sqrt(r2)); + acceleration[0] += coeff * position.getX(); + acceleration[1] += coeff * position.getY(); + acceleration[2] += coeff * position.getZ(); + } + + /** {@inheritDoc} */ + public void addXYZAcceleration(final double x, final double y, final double z) { + acceleration[0] += x; + acceleration[1] += y; + acceleration[2] += z; + } + + /** {@inheritDoc} */ + public void addAcceleration(final Vector3D gamma, final Frame frame) + throws OrekitException { + final Transform t = frame.getTransformTo(orbit.getFrame(), orbit.getDate()); + final Vector3D gammInRefFrame = t.transformVector(gamma); + addXYZAcceleration(gammInRefFrame.getX(), gammInRefFrame.getY(), gammInRefFrame.getZ()); + } + + } + +} diff --git a/src/main/java/org/orekit/propagation/numerical/ModeHandler.java b/src/main/java/org/orekit/propagation/numerical/ModeHandler.java index 9b055437bc..9b2e8f8844 100644 --- a/src/main/java/org/orekit/propagation/numerical/ModeHandler.java +++ b/src/main/java/org/orekit/propagation/numerical/ModeHandler.java @@ -1,4 +1,4 @@ -/* Copyright 2002-2010 CS Communication & Systèmes +/* Copyright 2010 Centre National d'Études Spatiales * Licensed to CS Communication & Systèmes (CS) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. @@ -16,7 +16,8 @@ */ package org.orekit.propagation.numerical; -import org.orekit.attitudes.AttitudeLaw; +import java.util.List; + import org.orekit.frames.Frame; import org.orekit.time.AbsoluteDate; @@ -27,11 +28,13 @@ import org.orekit.time.AbsoluteDate; public interface ModeHandler { /** Initialize the mode handler. + * @param mapper mapper between spacecraft state and simple array + * @param addStateAndEqu list of additional state and equations * @param reference reference date * @param frame reference frame * @param mu central body attraction coefficient - * @param attitudeLaw attitude law */ - void initialize(AbsoluteDate reference, Frame frame, double mu, AttitudeLaw attitudeLaw); + void initialize(StateMapper mapper, List <AdditionalStateAndEquations> addStateAndEqu, + AbsoluteDate reference, Frame frame, double mu); } diff --git a/src/main/java/org/orekit/propagation/numerical/NumericalPropagator.java b/src/main/java/org/orekit/propagation/numerical/NumericalPropagator.java index 687185fcee..ac2d5e6851 100644 --- a/src/main/java/org/orekit/propagation/numerical/NumericalPropagator.java +++ b/src/main/java/org/orekit/propagation/numerical/NumericalPropagator.java @@ -16,7 +16,9 @@ */ package org.orekit.propagation.numerical; +import java.lang.reflect.Field; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -26,16 +28,18 @@ import org.apache.commons.math.ode.FirstOrderDifferentialEquations; import org.apache.commons.math.ode.FirstOrderIntegrator; import org.apache.commons.math.ode.IntegratorException; import org.apache.commons.math.ode.events.EventHandler; -import org.apache.commons.math.ode.sampling.DummyStepHandler; -import org.orekit.attitudes.Attitude; +import org.apache.commons.math.ode.nonstiff.AdaptiveStepsizeIntegrator; import org.orekit.attitudes.AttitudeLaw; import org.orekit.attitudes.InertialLaw; import org.orekit.errors.OrekitException; import org.orekit.errors.OrekitMessages; import org.orekit.errors.PropagationException; import org.orekit.forces.ForceModel; +import org.orekit.forces.gravity.NewtonianAttraction; import org.orekit.frames.Frame; +import org.orekit.orbits.CartesianOrbit; import org.orekit.orbits.EquinoctialOrbit; +import org.orekit.orbits.Orbit; import org.orekit.propagation.BoundedPropagator; import org.orekit.propagation.Propagator; import org.orekit.propagation.SpacecraftState; @@ -125,49 +129,75 @@ import org.orekit.utils.PVCoordinates; */ public class NumericalPropagator implements Propagator { + /** Parameters types that can be used for propagation. */ + public enum PropagationParametersType { + + /** Type for propagation in cartesian parameters. */ + CARTESIAN, + + /** Type for propagation in equinoctial parameters. */ + EQUINOCTIAL + + } + /** Serializable UID. */ private static final long serialVersionUID = -2385169798425713766L; + /** Absolute vectorial error field name. */ + private static final String ABSOLUTE_TOLERANCE = "vecAbsoluteTolerance"; + + /** Relative vectorial error field name. */ + private static final String RELATIVE_TOLERANCE = "vecRelativeTolerance"; + // CHECKSTYLE: stop VisibilityModifierCheck /** Attitude law. */ - protected AttitudeLaw attitudeLaw; + private AttitudeLaw attitudeLaw; - /** Central body gravitational constant. */ - protected double mu; + /** Central body attraction. */ + private NewtonianAttraction newtonianAttraction; - /** Force models used during the extrapolation of the Orbit. */ - protected final List<ForceModel> forceModels; + /** Force models used during the extrapolation of the Orbit, without jacobians. */ + private final List<ForceModel> forceModels; /** Event detectors not related to force models. */ - protected final List<EventDetector> detectors; + private final List<EventDetector> detectors; /** State vector. */ - protected final double[] stateVector; + private double[] stateVector; /** Start date. */ - protected AbsoluteDate startDate; + private AbsoluteDate startDate; /** Initial state to propagate. */ - protected SpacecraftState initialState; + private SpacecraftState initialState; /** Current state to propagate. */ - protected SpacecraftState currentState; + private SpacecraftState currentState; /** Integrator selected by the user for the orbital extrapolation process. */ - protected transient FirstOrderIntegrator integrator; + private transient FirstOrderIntegrator integrator; /** Counter for differential equations calls. */ - protected int calls; + private int calls; /** Gauss equations handler. */ - protected TimeDerivativesEquations adder; + private TimeDerivativesEquations adder; + + /** Mapper between spacecraft state and simple array. */ + private StateMapper mapper; /** Propagator mode handler. */ - protected ModeHandler modeHandler; + private ModeHandler modeHandler; /** Current mode. */ - protected int mode; + private int mode; + + /** Propagation parameters type. */ + private PropagationParametersType type; + + /** Additional equations. */ + private List<AdditionalStateAndEquations> addStateAndEqu; // CHECKSTYLE: resume VisibilityModifierCheck @@ -176,20 +206,21 @@ public class NumericalPropagator implements Propagator { * unspecified default law and there are no perturbing forces at all. * This means that if {@link #addForceModel addForceModel} is not * called after creation, the integrated orbit will follow a keplerian - * evolution only. + * evolution only. The default parameter type for propagation is {@link + * PropagationParametersType#EQUINOCTIAL}. * @param integrator numerical integrator to use for propagation. */ public NumericalPropagator(final FirstOrderIntegrator integrator) { - this.mu = Double.NaN; - this.forceModels = new ArrayList<ForceModel>(); - this.detectors = new ArrayList<EventDetector>(); - this.startDate = new AbsoluteDate(); - this.currentState = null; - this.adder = null; - this.attitudeLaw = InertialLaw.EME2000_ALIGNED; - this.stateVector = new double[7]; + this.forceModels = new ArrayList<ForceModel>(); + this.detectors = new ArrayList<EventDetector>(); + this.startDate = new AbsoluteDate(); + this.currentState = null; + this.adder = null; + this.addStateAndEqu = new ArrayList<AdditionalStateAndEquations>(); + this.attitudeLaw = InertialLaw.EME2000_ALIGNED; setIntegrator(integrator); setSlaveMode(); + setPropagationParametersType(PropagationParametersType.EQUINOCTIAL); } /** Set the integrator. @@ -200,20 +231,20 @@ public class NumericalPropagator implements Propagator { } /** Set the central attraction coefficient μ. - * @param mu central attraction coefficient (m^3/s^2) + * @param mu central attraction coefficient (m<sup>3</sup>/s<sup>2</sup>) * @see #getMu() * @see #addForceModel(ForceModel) */ public void setMu(final double mu) { - this.mu = mu; + newtonianAttraction = new NewtonianAttraction(mu); } /** Get the central attraction coefficient μ. - * @return mu central attraction coefficient (m^3/s^2) + * @return mu central attraction coefficient (m<sup>3</sup>/s<sup>2</sup>) * @see #setMu(double) */ public double getMu() { - return mu; + return (newtonianAttraction == null) ? Double.NaN : newtonianAttraction.getMu(); } /** Set the attitude law. @@ -239,10 +270,6 @@ public class NumericalPropagator implements Propagator { } /** Add a force model to the global perturbation model. - * <p>If the force models is associated to discrete events, these - * events will be handled automatically, they must <strong>not</strong> - * be added using the {@link #addEventDetector(EventDetector) addEventDetector} - * method.</p> * <p>If this method is not called at all, the integrated orbit will follow * a keplerian evolution only.</p> * @param model perturbing {@link ForceModel} to add @@ -263,6 +290,24 @@ public class NumericalPropagator implements Propagator { forceModels.clear(); } + /** Get perturbing force models list. + * @return list of perturbing force models + * @see #addForceModel(ForceModel) + * @see #getNewtonianAttractionForceModel() + */ + public List<ForceModel> getForceModels() { + return forceModels; + } + + /** Get the Newtonian attraction from the central body force model. + * @return Newtonian attraction force model + * @see #setMu(double) + * @see #getForceModels() + */ + public NewtonianAttraction getNewtonianAttractionForceModel() { + return newtonianAttraction; + } + /** {@inheritDoc} */ public int getMode() { return mode; @@ -277,7 +322,6 @@ public class NumericalPropagator implements Propagator { */ public void setSlaveMode() { integrator.clearStepHandlers(); - integrator.addStepHandler(DummyStepHandler.getInstance()); modeHandler = null; mode = SLAVE_MODE; } @@ -287,7 +331,7 @@ public class NumericalPropagator implements Propagator { * of the underlying integrator set up in the {@link * #NumericalPropagator(FirstOrderIntegrator) constructor} or the {@link * #setIntegrator(FirstOrderIntegrator) setIntegrator} method. So if a specific - * step handler is needed, it should be added after this method has been callled.</p> + * step handler is needed, it should be added after this method has been called.</p> */ public void setMasterMode(final double h, final OrekitFixedStepHandler handler) { setMasterMode(new OrekitStepNormalizer(h, handler)); @@ -318,11 +362,25 @@ public class NumericalPropagator implements Propagator { public void setEphemerisMode() { integrator.clearStepHandlers(); final IntegratedEphemeris ephemeris = new IntegratedEphemeris(); - integrator.addStepHandler(ephemeris); modeHandler = ephemeris; + integrator.addStepHandler(ephemeris); mode = EPHEMERIS_GENERATION_MODE; } + /** Set propagation parameter type. + * @param propagationType parameters type to use for propagation + */ + public void setPropagationParametersType(final PropagationParametersType propagationType) { + this.type = propagationType; + } + + /** Get propagation parameter type. + * @return parameters type used for propagation + */ + public PropagationParametersType getPropagationParametersType() { + return type; + } + /** {@inheritDoc} */ public BoundedPropagator getGeneratedEphemeris() throws IllegalStateException { @@ -347,12 +405,59 @@ public class NumericalPropagator implements Propagator { /** {@inheritDoc} */ public void resetInitialState(final SpacecraftState state) { - if (Double.isNaN(mu)) { - mu = state.getMu(); + if (newtonianAttraction == null) { + setMu(state.getMu()); } this.initialState = state; } + /** Select additional state and equations pair in the list. + * @param addEqu Additional equations used as a reference for selection + * @return additional state and equations pair + * @throws OrekitException if additional equation is unknown */ + private AdditionalStateAndEquations selectStateAndEquations(final AdditionalEquations addEqu) + throws OrekitException { + for (AdditionalStateAndEquations stateAndEqu : addStateAndEqu) { + if (stateAndEqu.getAdditionalEquations() == addEqu) { + return stateAndEqu; + } + } + throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_EQUATION); + } + + /** Add a set of user-specified equations to be integrated along with the orbit propagation. + * @param addEqu additional equations + * @see #setInitialAdditionalState(double[], AdditionalEquations) + * @see #getCurrentAdditionalState(AdditionalEquations) + */ + public void addAdditionalEquations(final AdditionalEquations addEqu) { + addStateAndEqu.add(new AdditionalStateAndEquations(addEqu)); + } + + /** Set initial additional state. + * @param addState additional state + * @param addEqu additional equations used as a reference for selection + * @throws OrekitException if additional equation is unknown + * @see #addAdditionalEquations(AdditionalEquations) + * @see #getCurrentAdditionalState(AdditionalEquations) + */ + public void setInitialAdditionalState(final double[] addState, final AdditionalEquations addEqu) + throws OrekitException { + selectStateAndEquations(addEqu).setAdditionalState(addState); + } + + /** Get current additional state. + * @param addEqu additional equations used as a reference for selection + * @return current additional state + * @throws OrekitException if additional equation is unknown + * @see #addAdditionalEquations(AdditionalEquations) + * @see #setInitialAdditionalState(double[], AdditionalEquations) + */ + public double[] getCurrentAdditionalState(final AdditionalEquations addEqu) + throws OrekitException { + return selectStateAndEquations(addEqu).getAdditionalState(); + } + /** {@inheritDoc} */ public SpacecraftState propagate(final AbsoluteDate finalDate) throws PropagationException { @@ -371,18 +476,36 @@ public class NumericalPropagator implements Propagator { // space dynamics view startDate = initialState.getDate(); + + // set propagation parameters type + Orbit initialOrbit = null; + switch (type) { + case CARTESIAN : + initialOrbit = new CartesianOrbit(initialState.getOrbit()); + adder = new TimeDerivativesEquationsCartesian((CartesianOrbit) initialOrbit); + mapper = new StateMapperCartesian(); + break; + case EQUINOCTIAL : + initialOrbit = new EquinoctialOrbit(initialState.getOrbit()); + adder = new TimeDerivativesEquationsEquinoctial((EquinoctialOrbit) initialOrbit); + mapper = new StateMapperEquinoctial(); + break; + default : + throw OrekitException.createInternalError(null); + } + mapper.setAttitudeLaw(attitudeLaw); + + // initialize mode handler if (modeHandler != null) { - modeHandler.initialize(startDate, initialState.getFrame(), mu, attitudeLaw); + modeHandler.initialize(mapper, addStateAndEqu, startDate, initialState.getFrame(), getMu()); } - final EquinoctialOrbit initialOrbit = - new EquinoctialOrbit(initialState.getOrbit()); + // creating state vector + this.stateVector = new double[computeDimension()]; currentState = new SpacecraftState(initialOrbit, initialState.getAttitude(), initialState.getMass()); - adder = new TimeDerivativesEquations(initialOrbit); - if (initialState.getMass() <= 0.0) { throw new IllegalArgumentException("Mass is null or negative"); } @@ -392,13 +515,14 @@ public class NumericalPropagator implements Propagator { final double t1 = finalDate.durationFrom(startDate); // Map state to array - stateVector[0] = initialOrbit.getA(); - stateVector[1] = initialOrbit.getEquinoctialEx(); - stateVector[2] = initialOrbit.getEquinoctialEy(); - stateVector[3] = initialOrbit.getHx(); - stateVector[4] = initialOrbit.getHy(); - stateVector[5] = initialOrbit.getLv(); - stateVector[6] = initialState.getMass(); + mapper.mapStateToArray(initialState, stateVector); + int index = 7; + for (final AdditionalStateAndEquations stateAndEqu : addStateAndEqu) { + final double[] addState = stateAndEqu.getAdditionalState(); + System.arraycopy(addState, 0, stateVector, index, addState.length); + // Incrementing index + index += addState.length; + } integrator.clearEventHandlers(); @@ -418,17 +542,28 @@ public class NumericalPropagator implements Propagator { } // mathematical integration + if (!addStateAndEqu.isEmpty()) { + expandToleranceArray(); + } final double stopTime = integrator.integrate(new DifferentialEquations(), t0, stateVector, t1, stateVector); + if (!addStateAndEqu.isEmpty()) { + resetToleranceArray(); + } // back to space dynamics view final AbsoluteDate date = startDate.shiftedBy(stopTime); - final EquinoctialOrbit orbit = - new EquinoctialOrbit(stateVector[0], stateVector[1], stateVector[2], stateVector[3], - stateVector[4], stateVector[5], EquinoctialOrbit.TRUE_LATITUDE_ARGUMENT, - initialOrbit.getFrame(), date, mu); + // get final additional state + index = 7; + for (final AdditionalStateAndEquations stateAndEqu : addStateAndEqu) { + final double[] addState = stateAndEqu.getAdditionalState(); + System.arraycopy(stateVector, index, addState, 0, addState.length); + // Incrementing index + index += addState.length; + } - resetInitialState(new SpacecraftState(orbit, attitudeLaw.getAttitude(orbit), stateVector[6])); + // get final state + initialState = mapper.mapArrayToState(stateVector, date, getMu(), initialState.getFrame()); return initialState; } catch (OrekitException oe) { @@ -458,6 +593,56 @@ public class NumericalPropagator implements Propagator { } } + /** Expand integrator tolerance array to fit compound state vector. + */ + private void expandToleranceArray() { + if (integrator instanceof AdaptiveStepsizeIntegrator) { + final int n = computeDimension(); + resizeArray(integrator, ABSOLUTE_TOLERANCE, n, Double.POSITIVE_INFINITY); + resizeArray(integrator, RELATIVE_TOLERANCE, n, 0.0); + } + } + + /** Reset integrator tolerance array to original size. + */ + private void resetToleranceArray() { + if (integrator instanceof AdaptiveStepsizeIntegrator) { + final int n = stateVector.length; + resizeArray(integrator, ABSOLUTE_TOLERANCE, n, Double.POSITIVE_INFINITY); + resizeArray(integrator, RELATIVE_TOLERANCE, n, 0.0); + } + } + + /** Resize object internal array. + * @param instance instance concerned + * @param fieldName field name + * @param newSize new array size + * @param filler value to use to fill uninitialized elements of the new array + */ + private void resizeArray(final Object instance, final String fieldName, + final int newSize, final double filler) { + try { + final Field arrayField = AdaptiveStepsizeIntegrator.class.getDeclaredField(fieldName); + arrayField.setAccessible(true); + final double[] originalArray = (double[]) arrayField.get(instance); + final int originalSize = originalArray.length; + final double[] resizedArray = new double[newSize]; + if (newSize > originalSize) { + // expand array + System.arraycopy(originalArray, 0, resizedArray, 0, originalSize); + Arrays.fill(resizedArray, originalSize, newSize, filler); + } else { + // shrink array + System.arraycopy(originalArray, 0, resizedArray, 0, newSize); + } + arrayField.set(instance, resizedArray); + } catch (NoSuchFieldException nsfe) { + throw OrekitException.createInternalError(nsfe); + } catch (IllegalAccessException iae) { + throw OrekitException.createInternalError(iae); + } + } + /** {@inheritDoc} */ public PVCoordinates getPVCoordinates(final AbsoluteDate date, final Frame frame) throws OrekitException { @@ -478,14 +663,32 @@ public class NumericalPropagator implements Propagator { */ protected void setUpEventDetector(final EventDetector osf) { final EventHandler handler = - new AdaptedEventDetector(osf, startDate, mu, - initialState.getFrame(), attitudeLaw); + new AdaptedEventDetector(osf, mapper, startDate, getMu(), initialState.getFrame()); integrator.addEventHandler(handler, osf.getMaxCheckInterval(), osf.getThreshold(), osf.getMaxIterationCount()); } + /** Get state vector dimension without additional parameters. + * @return state vector dimension without additional parameters. + */ + public int getBasicDimension() { + return 7; + + } + /** Compute complete state vector dimension. + * @return state vector dimension + */ + private int computeDimension() { + int sum = getBasicDimension(); + for (final AdditionalStateAndEquations stateAndEqu : addStateAndEqu) { + sum += stateAndEqu.getAdditionalState().length; + } + return sum; + + } + /** Internal class for differential equations representation. */ private class DifferentialEquations implements FirstOrderDifferentialEquations { @@ -499,7 +702,7 @@ public class NumericalPropagator implements Propagator { /** {@inheritDoc} */ public int getDimension() { - return 7; + return computeDimension(); } /** {@inheritDoc} */ @@ -508,14 +711,15 @@ public class NumericalPropagator implements Propagator { try { // update space dynamics view - currentState = mapState(t, y, startDate, currentState.getFrame()); + currentState = mapper.mapArrayToState(y, startDate.shiftedBy(t), currentState.getMu(), currentState.getFrame()); + if (currentState.getMass() <= 0.0) { throw new PropagationException(OrekitMessages.SPACECRAFT_MASS_BECOMES_NEGATIVE, currentState.getMass()); } // initialize derivatives - adder.initDerivatives(yDot, (EquinoctialOrbit) currentState.getOrbit()); + adder.initDerivatives(yDot, currentState.getOrbit()); // compute the contributions of all perturbing forces for (final ForceModel forceModel : forceModels) { @@ -523,7 +727,26 @@ public class NumericalPropagator implements Propagator { } // finalize derivatives by adding the Kepler contribution - adder.addKeplerContribution(); + newtonianAttraction.addContribution(currentState, adder); + + // Add contribution for additional state + int index = 7; + for (final AdditionalStateAndEquations stateAndEqu : addStateAndEqu) { + final double[] p = stateAndEqu.getAdditionalState(); + final double[] pDot = stateAndEqu.getAdditionalStateDot(); + + // update current additional state + System.arraycopy(y, index, p, 0, p.length); + + // compute additional derivatives + stateAndEqu.getAdditionalEquations().computeDerivatives(currentState, adder, p, pDot); + + // update each additional state contribution in global array + System.arraycopy(pDot, 0, yDot, index, p.length); + + // incrementing index + index += p.length; + } // increment calls counter ++calls; @@ -534,31 +757,8 @@ public class NumericalPropagator implements Propagator { } - /** Convert state array to space dynamics objects (AbsoluteDate and OrbitalParameters). - * @param t integration time (s) - * @param y state as a flat array - * @param referenceDate reference date from which t is counted - * @param frame frame in which integration is performed - * @return state corresponding to the flat array as a space dynamics object - * @exception OrekitException if the attitude state cannot be determined - * by the attitude law - */ - private SpacecraftState mapState(final double t, final double [] y, - final AbsoluteDate referenceDate, final Frame frame) - throws OrekitException { - - // convert raw mathematical data to space dynamics objects - final AbsoluteDate date = referenceDate.shiftedBy(t); - final EquinoctialOrbit orbit = - new EquinoctialOrbit(y[0], y[1], y[2], y[3], y[4], y[5], - EquinoctialOrbit.TRUE_LATITUDE_ARGUMENT, - frame, date, mu); - final Attitude attitude = attitudeLaw.getAttitude(orbit); - - return new SpacecraftState(orbit, attitude, y[6]); - - } - } } + + diff --git a/src/main/java/org/orekit/propagation/numerical/NumericalPropagatorWithJacobians.java b/src/main/java/org/orekit/propagation/numerical/NumericalPropagatorWithJacobians.java deleted file mode 100644 index e77f34d784..0000000000 --- a/src/main/java/org/orekit/propagation/numerical/NumericalPropagatorWithJacobians.java +++ /dev/null @@ -1,530 +0,0 @@ -/* Copyright 2002-2010 CS Communication & Systèmes - * Licensed to CS Communication & Systèmes (CS) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * CS licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.orekit.propagation.numerical; - -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; - -import org.apache.commons.math.exception.MathUserException; -import org.apache.commons.math.ode.FirstOrderIntegrator; -import org.apache.commons.math.ode.IntegratorException; -import org.apache.commons.math.ode.jacobians.FirstOrderIntegratorWithJacobians; -import org.apache.commons.math.ode.jacobians.ODEWithJacobians; -import org.apache.commons.math.ode.jacobians.ParameterizedODE; -import org.apache.commons.math.ode.nonstiff.AdaptiveStepsizeIntegrator; -import org.apache.commons.math.util.FastMath; -import org.apache.commons.math.util.MathUtils; -import org.orekit.attitudes.Attitude; -import org.orekit.errors.OrekitException; -import org.orekit.errors.OrekitMessages; -import org.orekit.errors.PropagationException; -import org.orekit.forces.ForceModel; -import org.orekit.forces.ForceModelWithJacobians; -import org.orekit.forces.Parameterizable; -import org.orekit.frames.Frame; -import org.orekit.orbits.EquinoctialOrbit; -import org.orekit.propagation.SpacecraftState; -import org.orekit.propagation.events.EventDetector; -import org.orekit.time.AbsoluteDate; - - -/** This class propagates {@link org.orekit.orbits.Orbit orbits} using - * numerical integration and enables jacobians computation for orbit parameters - * and partial derivatives computation with respect to some force models parameters. - * <p> - * As of 5.0, this class is still considered experimental, so use it with care, - * the API could change in the future. - * </p> - * <p> - * The underlying numerical integrator configuration can be exactly the same - * as these for a simple {@link NumericalPropagator numerical integration}. - * </p> - * <p> - * The Jacobian for the six {@link EquinoctialOrbit equinoctial orbit parameters} - * (a, e<sub>x</sub>, e<sub>y</sub>, h<sub>x</sub>, h<sub>y</sub>, l<sub>v</sub>) - * and the mass is computed as a 7x7 array such as: - * <pre> - * dFdY[i][j] = dyi/dyj - * with: y0 = a, y1 = ex, y2 = ey, y3 = hx, y4 = hy, y5 = lv, y6 = mass - * </pre> - * </p> - * <p> - * Partial derivatives can also be computed for the 7 elements state vector with - * respect to n {@link #selectParameters selected parameters} from - * {@link ForceModelWithJacobians force models}. They are computed as a 7xn array: - * <pre> - * dFdP[i][j] = dyi/dpj - * </pre> - * </p> - * - * @see NumericalPropagator - * @see ForceModelWithJacobians - * - * @author Pascal Parraud - * @version $Revision$ $Date$ - */ -public class NumericalPropagatorWithJacobians extends NumericalPropagator { - - /** Serializable UID. */ - private static final long serialVersionUID = 4139595812211569107L; - - /** Force models used when extrapolating the Orbit. */ - private final List<ForceModelWithJacobians> forceModelsWJ; - - /** State vector derivative with respect to the parameter. */ - private double[][] DY0DP = null; - - /** Gauss equations handler. */ - private TimeDerivativesEquationsWithJacobians adder; - - /** Selected parameters for jacobian computation. */ - private String[] selectedParameters = new String[0]; - - /** Selected parameters with force model associated for jacobian computation. */ - private List<ParameterPair> paramPairs = new ArrayList<ParameterPair>(); - - /** Create a new instance of NumericalPropagatorWithJacobians. - * After creation, the instance is empty, i.e. the attitude law is set to an - * unspecified default law and there are no perturbing forces at all. - * This means that if {@link #addForceModel addForceModel} is not - * called after creation, the integrated orbit will follow a keplerian - * evolution only. - * @param integrator numerical integrator to use for propagation. - */ - public NumericalPropagatorWithJacobians(final FirstOrderIntegrator integrator) { - super(integrator); - this.forceModelsWJ = new ArrayList<ForceModelWithJacobians>(); - } - - /** Add a force model to the global perturbation model. - * @param model perturbing force model to add - * @see #removeForceModels() - * @see #removeForceModels() - */ - public void addForceModel(final ForceModel model) { - forceModels.add(model); - if (!(model instanceof ForceModelWithJacobians)) { - forceModelsWJ.add(new ForceModelWrapper(model)); - } else { - forceModelsWJ.add((ForceModelWithJacobians) model); - } - } - - /** Remove all perturbing force models from the global perturbation model. - * <p>Once all perturbing forces have been removed (and as long as no new force - * model is added), the integrated orbit will follow a keplerian evolution - * only.</p> - * @see #addForceModel(ForceModel) - */ - public void removeForceModels() { - forceModels.clear(); - forceModelsWJ.clear(); - } - - /** Select the parameters to consider for jacobian processing. - * <p>Parameters names have to be consistent with some - * {@link ForceModelWithJacobians} added elsewhere.</p> - * @param parameters parameters to consider for jacobian processing - * @see #addForceModel(ForceModel) - * @see ForceModelWithJacobians - * @see Parameterizable - */ - public void selectParameters(final String[] parameters) { - selectedParameters = parameters.clone(); - DY0DP = new double[7][selectedParameters.length]; - for (final double[] row : DY0DP) { - Arrays.fill(row, 0.0); - } - } - - /** Get the parameters selected for jacobian processing. - * @return parameters considered for jacobian processing - * @see #selectParameters(String[]) - */ - public String[] getParameterNames() { - return selectedParameters.clone(); - } - - /** Propagate towards a target date and compute partial derivatives. - * <p>Propagation is the same as the - * {@link NumericalPropagator#propagate(AbsoluteDate) basic one}.</p> - * <p>Jacobian for orbit parameters is given as a 7x7 array.</p> - * <p>Partial derivatives will be computed as a 7xn array - * when n parameters have been {@link #selectParameters(String[]) selected} - * (n may be 0).</p> - * <p>Those parameters are related to some {@link ForceModelWithJacobians force models} - * which must have been added elsewhere.</p> - * @param finalDate target date towards which orbit state should be propagated - * @param dFdY equinoctial orbit parameters + mass jacobian (7x7 array) - * @param dFdP partial derivatives with respect to selected parameters (7xn) - * @return propagated state - * @exception PropagationException if state cannot be propagated - */ - public SpacecraftState propagate(final AbsoluteDate finalDate, - final double[][] dFdY, final double[][] dFdP) - throws PropagationException { - try { - - if (initialState == null) { - throw new PropagationException(OrekitMessages.INITIAL_STATE_NOT_SPECIFIED_FOR_ORBIT_PROPAGATION); - } - if (initialState.getMass() <= 0.0) { - throw new PropagationException(OrekitMessages.NOT_POSITIVE_SPACECRAFT_MASS, initialState.getMass()); - } - if (initialState.getDate().equals(finalDate)) { - // don't extrapolate - return initialState; - } - if (integrator == null) { - throw new PropagationException(OrekitMessages.ODE_INTEGRATOR_NOT_SET_FOR_ORBIT_PROPAGATION); - } - - // space dynamics view - startDate = initialState.getDate(); - if (modeHandler != null) { - modeHandler.initialize(startDate, initialState.getFrame(), mu, attitudeLaw); - } - - final EquinoctialOrbit initialOrbit = - new EquinoctialOrbit(initialState.getOrbit()); - - currentState = - new SpacecraftState(initialOrbit, initialState.getAttitude(), initialState.getMass()); - - adder = new TimeDerivativesEquationsWithJacobians(initialOrbit); - - integrator.clearEventHandlers(); - - // set up events related to force models - for (final ForceModelWithJacobians forceModel : forceModelsWJ) { - final EventDetector[] modelDetectors = forceModel.getEventsDetectors(); - if (modelDetectors != null) { - for (final EventDetector detector : modelDetectors) { - setUpEventDetector(detector); - } - } - } - - // set up events added by user - for (final EventDetector detector : detectors) { - setUpEventDetector(detector); - } - - // mathematical view - final double t0 = 0; - final double t1 = finalDate.durationFrom(startDate); - - // Map state to array - stateVector[0] = initialOrbit.getA(); - stateVector[1] = initialOrbit.getEquinoctialEx(); - stateVector[2] = initialOrbit.getEquinoctialEy(); - stateVector[3] = initialOrbit.getHx(); - stateVector[4] = initialOrbit.getHy(); - stateVector[5] = initialOrbit.getLv(); - stateVector[6] = initialState.getMass(); - - // set up parameters for jacobian computation - int noParam = 0; - final int nbParam = selectedParameters.length; - final double[] paramWJ = new double[nbParam]; - final double[] hP = new double[nbParam]; - - for (final String parameter : selectedParameters) { - boolean found = false; - for (final ForceModelWithJacobians fmwj : forceModelsWJ) { - for (String parFMWJ : fmwj.getParametersNames()) { - if (parFMWJ.matches(parameter)) { - found = true; - paramWJ[noParam] = fmwj.getParameter(parFMWJ); - hP[noParam] = paramWJ[noParam] * FastMath.sqrt(MathUtils.EPSILON); - paramPairs.add(new ParameterPair(parameter, fmwj)); - noParam++; - } - } - } - if (!found) { - throw new PropagationException(OrekitMessages.UNKNOWN_PARAMETER, parameter); - } - } - - // if selectParameters was not invoked and then no parameter selected - if (DY0DP == null) { - DY0DP = new double[7][nbParam]; - for (final double[] row : DY0DP) { - Arrays.fill(row, 0.0); - } - } - - // get hY from integrator tolerance array - final double[] hY = getHy(integrator); - - final FirstOrderIntegratorWithJacobians integratorWJ = - new FirstOrderIntegratorWithJacobians(integrator, - new DifferentialEquations(), - paramWJ, hY, hP); - - // mathematical integration - final double stopTime = integratorWJ.integrate(t0, stateVector, DY0DP, - t1, stateVector, dFdY, DY0DP); - // fill in jacobian - for (int i = 0; i < DY0DP.length; i++) { - System.arraycopy(DY0DP[i], 0, dFdP[i], 0, DY0DP[i].length); - } - - // back to space dynamics view - final AbsoluteDate date = startDate.shiftedBy(stopTime); - - final EquinoctialOrbit orbit = - new EquinoctialOrbit(stateVector[0], stateVector[1], stateVector[2], stateVector[3], - stateVector[4], stateVector[5], EquinoctialOrbit.TRUE_LATITUDE_ARGUMENT, - initialOrbit.getFrame(), date, mu); - - resetInitialState(new SpacecraftState(orbit, attitudeLaw.getAttitude(orbit), stateVector[6])); - - return initialState; - - } catch (OrekitException oe) { - throw new PropagationException(oe); - } catch (MathUserException mue) { - - // recover a possible embedded PropagationException - for (Throwable t = mue; t != null; t = t.getCause()) { - if (t instanceof PropagationException) { - throw (PropagationException) t; - } - } - - throw new PropagationException(mue, mue.getGeneralPattern(), mue.getArguments()); - - } catch (IntegratorException ie) { - - // recover a possible embedded PropagationException - for (Throwable t = ie; t != null; t = t.getCause()) { - if (t instanceof PropagationException) { - throw (PropagationException) t; - } - } - - throw new PropagationException(ie, ie.getGeneralPattern(), ie.getArguments()); - - } - } - - /** Get hY from integrator absolute tolerance array. - * @param integrator integrator - * @return step sizes array for df/dy computing - */ - private double[] getHy(final FirstOrderIntegrator integrator) { - double[] hY = new double[0]; - if (integrator instanceof AdaptiveStepsizeIntegrator) { - try { - final Field arrayField = AdaptiveStepsizeIntegrator.class.getDeclaredField("vecAbsoluteTolerance"); - arrayField.setAccessible(true); - hY = (double[]) arrayField.get(integrator); - for (int i = 0; i < hY.length; i++) { - hY[i] *= 10.; - } - } catch (NoSuchFieldException nsfe) { - throw OrekitException.createInternalError(nsfe); - } catch (IllegalAccessException iae) { - throw OrekitException.createInternalError(iae); - } - } - return hY; - } - - /** Internal class for differential equations representation. */ - private class DifferentialEquations implements ParameterizedODE, ODEWithJacobians { - - /** Build a new instance. */ - protected DifferentialEquations() { - calls = 0; - } - - /** {@inheritDoc} */ - public int getDimension() { - return 7; - } - - /** {@inheritDoc} */ - public void computeDerivatives(final double t, final double[] y, final double[] yDot) - throws MathUserException { - - try { - // update space dynamics view - currentState = mapState(t, y, startDate, currentState.getFrame()); - - // compute cartesian coordinates - if (currentState.getMass() <= 0.0) { - throw new PropagationException(OrekitMessages.SPACECRAFT_MASS_BECOMES_NEGATIVE, - currentState.getMass()); - } - - // initialize derivatives - adder.initDerivatives(yDot, (EquinoctialOrbit) currentState.getOrbit()); - - // compute the contributions of all perturbing forces - for (final ForceModel forceModel : forceModels) { - forceModel.addContribution(currentState, adder); - } - - // finalize derivatives by adding the Kepler contribution - adder.addKeplerContribution(); - - // increment calls counter - ++calls; - - } catch (OrekitException oe) { - throw new MathUserException(oe, oe.getSpecifier(), oe.getParts()); - } - - } - - /** {@inheritDoc} */ - public void computeJacobians(final double t, final double[] y, final double[] yDot, - final double[][] dFdY, final double[][] dFdP) { - } - - /** {@inheritDoc} */ - public int getParametersDimension() { - return selectedParameters.length; - } - - /** {@inheritDoc} */ - public void setParameter(final int index, final double value) { - final ParameterPair pp = paramPairs.get(index); - pp.getParamHandler().setParameter(pp.getParamName(), value); - } - - /** Convert state array to space dynamics objects (AbsoluteDate and OrbitalParameters). - * @param t integration time (s) - * @param y state as a flat array - * @param referenceDate reference date from which t is counted - * @param frame frame in which integration is performed - * @return state corresponding to the flat array as a space dynamics object - * @exception OrekitException if the attitude state cannot be determined - * by the attitude law - */ - private SpacecraftState mapState(final double t, final double [] y, - final AbsoluteDate referenceDate, final Frame frame) - throws OrekitException { - - // convert raw mathematical data to space dynamics objects - final AbsoluteDate date = referenceDate.shiftedBy(t); - final EquinoctialOrbit orbit = - new EquinoctialOrbit(y[0], y[1], y[2], y[3], y[4], y[5], - EquinoctialOrbit.TRUE_LATITUDE_ARGUMENT, - frame, date, mu); - final Attitude attitude = attitudeLaw.getAttitude(orbit); - - return new SpacecraftState(orbit, attitude, y[6]); - - } - - } - - /** Internal class used to pair a parameter name with its handler. */ - private static class ParameterPair { - - /** Parameter name. */ - private final String paramName; - - /** Parameter handler. */ - private final Parameterizable paramHandler; - - /** Simple constructor. - * @param paramName parameter name - * @param paramHandler force model handling the parameter - */ - public ParameterPair(final String paramName, final Parameterizable paramHandler) { - this.paramName = paramName; - this.paramHandler = paramHandler; - } - - /** Get parameter name. - * @return parameter name - */ - public String getParamName() { - return paramName; - } - - /** Get parameter handler. - * @return force model handling the parameter - */ - public Parameterizable getParamHandler() { - return paramHandler; - } - - } - - /** Internal class enabling basic force model - * to be used when processing parameters jacobian. - */ - private static class ForceModelWrapper implements ForceModelWithJacobians { - - /** Serializable UID. */ - private static final long serialVersionUID = 3625153851142193056L; - - /** Wrapped basic force model. */ - private final ForceModel basic; - - /** Simple constructor. - * @param basic force model to wrap - */ - public ForceModelWrapper(final ForceModel basic) { - this.basic = basic; - } - - /** {@inheritDoc} */ - public void addContribution(final SpacecraftState s, - final TimeDerivativesEquations adder) - throws OrekitException { - basic.addContribution(s, adder); - } - - /** {@inheritDoc} */ - public EventDetector[] getEventsDetectors() { - return basic.getEventsDetectors(); - } - - /** {@inheritDoc} */ - public void addContributionWithJacobians(final SpacecraftState s, - final TimeDerivativesEquationsWithJacobians adder) - throws OrekitException { - } - - /** {@inheritDoc} */ - public double getParameter(final String name) throws IllegalArgumentException { - return Double.NaN; - } - - /** {@inheritDoc} */ - public Collection<String> getParametersNames() { - return new ArrayList<String>(); - } - - /** {@inheritDoc} */ - public void setParameter(final String name, final double value) - throws IllegalArgumentException { - } - - } - -} diff --git a/src/main/java/org/orekit/propagation/numerical/ParameterConfiguration.java b/src/main/java/org/orekit/propagation/numerical/ParameterConfiguration.java new file mode 100644 index 0000000000..bd892f1306 --- /dev/null +++ b/src/main/java/org/orekit/propagation/numerical/ParameterConfiguration.java @@ -0,0 +1,77 @@ +/* Copyright 2010 Centre National d'Études Spatiales + * Licensed to CS Communication & Systèmes (CS) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * CS licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.orekit.propagation.numerical; + +import java.io.Serializable; + +/** Simple container associating a parameter name with a step to compute its jacobian + * and the provider thant manages it. + * @author Véronique Pommier-Maurussane + * @version $Revision$ $Date$ + */ +class ParameterConfiguration implements Serializable { + + /** Serializable UID. */ + private static final long serialVersionUID = 2247518849090889379L; + + /** Parameter name. */ + private String parameterName; + + /** Parameter step for finite difference computation of partial derivative with respect to that parameter. */ + private double hP; + + /** Provider handling this parameter. */ + private AccelerationJacobiansProvider provider; + + /** Parameter name and step pair constructor. + * @param parameterName parameter name + * @param hP parameter step */ + public ParameterConfiguration(final String parameterName, final double hP) { + this.parameterName = parameterName; + this.hP = hP; + this.provider = null; + } + + /** Get parameter name. + * @return parameterName parameter name + */ + public String getParameterName() { + return parameterName; + } + + /** Get parameter step. + * @return hP parameter step + */ + public double getHP() { + return hP; + } + + /** Set the povider handling this parameter. + * @param provider provider handling this parameter + */ + public void setProvider(final AccelerationJacobiansProvider provider) { + this.provider = provider; + } + + /** Get the povider handling this parameter. + * @return provider handling this parameter + */ + public AccelerationJacobiansProvider getProvider() { + return provider; + } + +} diff --git a/src/main/java/org/orekit/propagation/numerical/PartialDerivativesEquations.java b/src/main/java/org/orekit/propagation/numerical/PartialDerivativesEquations.java new file mode 100644 index 0000000000..9fd2d9a41d --- /dev/null +++ b/src/main/java/org/orekit/propagation/numerical/PartialDerivativesEquations.java @@ -0,0 +1,454 @@ +/* Copyright 2010 Centre National d'Études Spatiales + * Licensed to CS Communication & Systèmes (CS) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * CS licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.orekit.propagation.numerical; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.math.util.FastMath; +import org.apache.commons.math.util.MathUtils; +import org.orekit.errors.OrekitException; +import org.orekit.errors.OrekitMessages; +import org.orekit.forces.ForceModel; +import org.orekit.propagation.SpacecraftState; + +/** Set of {@link AdditionalEquations additional equations} computing the partial derivatives + * of the state (orbit) with respect to initial state and force models parameters. + * <p> + * This set of equations can be added to a {@link NumericalPropagator numerical propagator} + * in order to compute partial derivatives of the orbit along with the orbit itself. This is + * useful for example in orbit determination applications. + * </p> + * @author Véronique Pommier-Maurussane + * @version $Revision$ $Date$ + */ +public class PartialDerivativesEquations implements AdditionalEquations { + + /** Serializable UID. */ + private static final long serialVersionUID = 8373349999733456541L; + + /** Selected parameters for jacobian computation. */ + private NumericalPropagator propagator; + + /** Jacobians providers. */ + private final List<AccelerationJacobiansProvider> jacobiansProviders; + + /** List of parameters selected for jacobians computation. */ + private List<ParameterConfiguration> selectedParameters; + + /** State vector dimension without additional parameters + * (either 6 or 7 depending on mass derivatives being included or not). */ + private int stateDim; + + /** Parameters vector dimension. */ + private int paramDim; + + /** Step used for finite difference computation with respect to spacecraft position. */ + private double hPos; + + /** Step used for finite difference computation with respect to spacecraft velocity. */ + private double hVel; + + /** Step used for finite difference computation with respect to spacecraft mass. */ + private double hM; + + /** Boolean for force models / selected parameters consistency. */ + private boolean dirty = false; + + /** Jacobian of acceleration with respect to spacecraft position. */ + private transient double[][] dAccdPos; + + /** Jacobian of acceleration with respect to spacecraft velocity. */ + private transient double[][] dAccdVel; + + /** Jacobian of acceleration with respect to spacecraft mass. */ + private transient double[] dAccdM; + + /** Jacobian of acceleration with respect to one force model parameter (array reused for all parameters). */ + private transient double[] dAccdParam; + + /** Simple constructor. + * @param propagator the propagator that will handle the orbit propagation + */ + public PartialDerivativesEquations(final NumericalPropagator propagator) { + jacobiansProviders = new ArrayList<AccelerationJacobiansProvider>(); + dirty = true; + this.propagator = propagator; + selectedParameters = new ArrayList<ParameterConfiguration>(); + stateDim = -1; + paramDim = -1; + hPos = Double.NaN; + hVel = Double.NaN; + hM = Double.NaN; + } + + /** Get the names of the available parameters in the propagator. + * <p> + * The names returned depend on the force models set up in the propagator, + * including the Newtonian attraction from the central body. + * </p> + * @return available parameters + */ + public List<String> getAvailableParameters() { + final List<String> available = new ArrayList<String>(); + available.addAll(propagator.getNewtonianAttractionForceModel().getParametersNames()); + for (final ForceModel model : propagator.getForceModels()) { + available.addAll(model.getParametersNames()); + } + return available; + } + + /** Select the parameters to consider for jacobian processing. + * <p>Parameters names have to be consistent with some + * {@link ForceModel} added elsewhere.</p> + * @param parameters parameters to consider for jacobian processing + * @see NumericalPropagator#addForceModel(ForceModel) + * @see #setInitialJacobians(double[][], double[][]) + * @see ForceModel + * @see org.orekit.forces.Parameterizable + */ + public void selectParameters(final String ... parameters) { + + selectedParameters.clear(); + for (String param : parameters) { + selectedParameters.add(new ParameterConfiguration(param, Double.NaN)); + } + dirty = true; + } + + /** Select the parameters to consider for jacobian processing. + * <p>Parameters names have to be consistent with some + * {@link ForceModel} added elsewhere.</p> + * @param parameter parameter to consider for jacobian processing + * @param hP step to use for computing jacobian column with respect to the specified parameter + * @see NumericalPropagator#addForceModel(ForceModel) + * @see #setInitialJacobians(double[][], double[][]) + * @see ForceModel + * @see org.orekit.forces.Parameterizable + */ + public void selectParamAndStep(final String parameter, final double hP) { + selectedParameters.add(new ParameterConfiguration(parameter, hP)); + dirty = true; + } + + /** Set the steps for finite differences with respect to spacecraft state. + * @param hPosition step used for finite difference computation with respect to spacecraft position (m) + * @param hVelocity step used for finite difference computation with respect to spacecraft velocity (m/s) + * @param hMass step used for finite difference computation with respect to spacecraft mass (kg) + */ + public void setSteps(final double hPosition, final double hVelocity, final double hMass) { + this.hPos = hPosition; + this.hVel = hVelocity; + this.hM = hMass; + } + + /** Set the initial value of the jacobian with respect to state and parameter. + * <p> + * This method is equivalent to call {@link #setInitialJacobians(double[][], double[][])} + * with dYdY0 set to the identity matrix and dYdP set to a zero matrix. + * </p> + * @param stateDimension state dimension, must be either 6 for orbit only or 7 for orbit and mass + * @param paramDimension parameters dimension + * @exception OrekitException if the partial equation has not been registered in + * the propagator or if matrices dimensions are incorrect + * @see #selectedParameters + * @see #selectParamAndStep(String, double) + */ + public void setInitialJacobians(final int stateDimension, final int paramDimension) + throws OrekitException { + final double[][] dYdY0 = new double[stateDimension][stateDimension]; + final double[][] dYdP = new double[stateDimension][paramDimension]; + for (int i = 0; i < stateDimension; ++i) { + dYdY0[i][i] = 1.0; + } + setInitialJacobians(dYdY0, dYdP); + } + + /** Set the initial value of the jacobian with respect to state and parameter. + * @param dYdY0 initial jacobian w.r to state (may be either 6x6 for orbit only + * or 7x7 for orbit and mass) + * @param dYdP initial jacobian w.r to parameter (may be null if no parameters are selected) + * @exception OrekitException if the partial equation has not been registered in + * the propagator or if matrices dimensions are incorrect + * @see #selectedParameters + * @see #selectParamAndStep(String, double) + */ + public void setInitialJacobians(final double[][] dYdY0, final double[][] dYdP) + throws OrekitException { + + // Check dimensions + stateDim = dYdY0.length; + if ((stateDim < 6) || (stateDim > 7) || (stateDim != dYdY0[0].length)) { + throw new OrekitException(OrekitMessages.STATE_JACOBIAN_NEITHER_6X6_NOR_7X7, + stateDim, dYdY0[0].length); + } + if ((dYdP != null) && (stateDim != dYdP.length)) { + throw new OrekitException(OrekitMessages.STATE_AND_PARAMETERS_JACOBIANS_ROWS_MISMATCH, + stateDim, dYdP.length); + } + + paramDim = (dYdP == null) ? 0 : dYdP[0].length; + + // store the matrices in row major order as a single dimension array + final double[] p = new double[stateDim * (stateDim + paramDim)]; + int index = 0; + for (final double[] row : dYdY0) { + System.arraycopy(row, 0, p, index, stateDim); + index += stateDim; + } + + if (dYdP != null) { + for (final double[] row : dYdP) { + System.arraycopy(row, 0, p, index, paramDim); + index += paramDim; + } + } + + // set value in propagator + propagator.setInitialAdditionalState(p, this); + + } + + /** Get the initial value of the jacobian with respect to state and parameter. + * @param dYdY0 current jacobian w.r to state. + * @param dYdP current jacobian w.r to parameter (may be null if no parameters are selected) + * @exception OrekitException if the partial equation has not been registered in + * the propagator + */ + public void getCurrentJacobians(final double[][] dYdY0, final double[][] dYdP) + throws OrekitException { + + // get current state from propagator + final double[] p = propagator.getCurrentAdditionalState(this); + + int index = 0; + for (int i = 0; i < stateDim; i++) { + System.arraycopy(p, index, dYdY0[i], 0, stateDim); + index += stateDim; + } + + if (paramDim != 0) { + for (int i = 0; i < stateDim; i++) { + System.arraycopy(p, index, dYdP[i], 0, paramDim); + index += paramDim; + } + } + + } + + /** {@inheritDoc} */ + public void computeDerivatives(final SpacecraftState s, final TimeDerivativesEquations adder, + final double[] p, final double[] pDot) throws OrekitException { + + final int dim = 3; + + // Lazy initialization + if (dirty) { + + if (propagator.getPropagationParametersType() != NumericalPropagator.PropagationParametersType.CARTESIAN) { + throw new OrekitException(OrekitMessages.PARTIAL_DERIVATIVES_ONLY_IN_CARTESIAN); + } + + // if steps have not been set by user, set default values + if (Double.isNaN(hPos)) { + final double factor = FastMath.sqrt(MathUtils.EPSILON); + hPos = factor * s.getPVCoordinates().getPosition().getNorm(); + hVel = factor * s.getPVCoordinates().getVelocity().getNorm(); + hM = factor * s.getMass(); + } + + // set up jacobians providers + jacobiansProviders.clear(); + for (final ForceModel model : propagator.getForceModels()) { + if (model instanceof AccelerationJacobiansProvider) { + + // the force model already provides the jacobians by itself + jacobiansProviders.add((AccelerationJacobiansProvider) model); + + } else { + + // wrap the force model to compute the jacobians by finite differences + jacobiansProviders.add(new Jacobianizer(model, selectedParameters, hPos, hVel, hM)); + + } + } + jacobiansProviders.add(propagator.getNewtonianAttractionForceModel()); + + // check all parameters are handled by at least one jacobian provider + for (final ParameterConfiguration param : selectedParameters) { + final String name = param.getParameterName(); + boolean found = false; + for (final AccelerationJacobiansProvider provider : jacobiansProviders) { + if (provider.isSupported(name)) { + param.setProvider(provider); + found = true; + } + } + if (!found) { + throw new OrekitException(OrekitMessages.UNKNOWN_PARAMETER, name); + } + } + + // check the numbers of parameters and matrix size agree + if (selectedParameters.size() != paramDim) { + throw new OrekitException(OrekitMessages.INITIAL_MATRIX_AND_PARAMETERS_NUMBER_MISMATCH, + paramDim, selectedParameters.size()); + } + + dAccdParam = new double[dim]; + dAccdPos = new double[dim][dim]; + dAccdVel = new double[dim][dim]; + dAccdM = (stateDim > 6) ? new double[dim] : null; + + dirty = false; + + } + + // compute forces gradients dAccDState + for (final double[] row : dAccdPos) { + Arrays.fill(row, 0.0); + } + for (final double[] row : dAccdVel) { + Arrays.fill(row, 0.0); + } + if (dAccdM != null) { + Arrays.fill(dAccdM, 0.0); + } + for (final AccelerationJacobiansProvider jacobProv : jacobiansProviders) { + jacobProv.addDAccDState(s, dAccdPos, dAccdVel, dAccdM); + } + + // the variational equations of the complete state jacobian matrix have the + // following form for 7x7, i.e. when mass partial derivatives are also considered + // (when mass is not considered, only the A, B, D and E matrices are used along + // with their derivatives): + + // [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] + // [ Adot ] [ Bdot ] [ Cdot ] [ dVel/dPos = 0 ] [ dVel/dVel = Id ] [ dVel/dm = 0 ] [ A ] [ B ] [ C ] + // [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] + // --------+--------+--- ---- ------------------+------------------+---------------- -----+-----+----- + // [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] + // [ Ddot ] [ Edot ] [ Fdot ] = [ dAcc/dPos ] [ dAcc/dVel ] [ dAcc/dm ] * [ D ] [ E ] [ F ] + // [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] + // --------+--------+--- ---- ------------------+------------------+---------------- -----+-----+----- + // [ Gdot ] [ Hdot ] [ Idot ] [ dmDot/dPos = 0 ] [ dmDot/dVel = 0 ] [ dmDot/dm = 0 ] [ G ] [ H ] [ I ] + + // The A, B, D and E sub-matrices and their derivatives (Adot ...) are 3x3 matrices, + // the C and F sub-matrices and their derivatives (Cdot ...) are 3x1 matrices, + // the G and H sub-matrices and their derivatives (Gdot ...) are 1x3 matrices, + // the I sub-matrix and its derivative (Idot) are 1x1 matrices. + + // The expanded multiplication above can be rewritten to take into account + // the fixed values found in the sub-matrices in the left factor. This leads to: + + // [ Adot ] = [ D ] + // [ Bdot ] = [ E ] + // [ Cdot ] = [ F ] + // [ Ddot ] = [ dAcc/dPos ] * [ A ] + [ dAcc/dVel ] * [ D ] + [ dAcc/dm ] * [ G ] + // [ Edot ] = [ dAcc/dPos ] * [ B ] + [ dAcc/dVel ] * [ E ] + [ dAcc/dm ] * [ H ] + // [ Fdot ] = [ dAcc/dPos ] * [ C ] + [ dAcc/dVel ] * [ F ] + [ dAcc/dm ] * [ I ] + // [ Gdot ] = [ 0 ] + // [ Hdot ] = [ 0 ] + // [ Idot ] = [ 0 ] + + // The following loops compute these expressions taking care of the mapping of the + // (A, B, ... I) matrices into the single dimension array p and of the mapping of the + // (Adot, Bdot, ... Idot) matrices into the single dimension array pDot. + + // copy D, E and F into Adot, Bdot and Cdot + System.arraycopy(p, dim * stateDim, pDot, 0, dim * stateDim); + + // compute Ddot, Edot and Fdot + for (int i = 0; i < dim; ++i) { + final double[] dAdPi = dAccdPos[i]; + final double[] dAdVi = dAccdVel[i]; + for (int j = 0; j < stateDim; ++j) { + pDot[(dim + i) * stateDim + j] = + dAdPi[0] * p[j] + dAdPi[1] * p[j + stateDim] + dAdPi[2] * p[j + 2 * stateDim] + + dAdVi[0] * p[j + 3 * stateDim] + dAdVi[1] * p[j + 4 * stateDim] + dAdVi[2] * p[j + 5 * stateDim] + + ((dAccdM == null) ? 0.0 : dAccdM[i] * p[j + 6 * stateDim]); + } + } + + if (dAccdM != null) { + // set Gdot, Hdot and Idot to 0 + Arrays.fill(pDot, 6 * stateDim, 7 * stateDim, 0.0); + } + + for (int k = 0; k < paramDim; ++k) { + + // compute the acceleration gradient with respect to current parameter + final ParameterConfiguration param = selectedParameters.get(k); + final AccelerationJacobiansProvider provider = param.getProvider(); + Arrays.fill(dAccdParam, 0.0); + provider.addDAccDParam(s, param.getParameterName(), dAccdParam); + + // the variational equations of the parameters jacobian matrix are computed + // one column at a time, they have the following form: + // [ ] [ ] [ ] [ ] [ ] [ ] + // [ Jdot ] [ dVel/dPos = 0 ] [ dVel/dVel = Id ] [ dVel/dm = 0 ] [ J ] [ dVel/dParam = 0 ] + // [ ] [ ] [ ] [ ] [ ] [ ] + // -------- ------------------+------------------+---------------- ----- -------------------- + // [ ] [ ] [ ] [ ] [ ] [ ] + // [ Kdot ] = [ dAcc/dPos ] [ dAcc/dVel ] [ dAcc/dm ] * [ K ] + [ dAcc/dParam ] + // [ ] [ ] [ ] [ ] [ ] [ ] + // -------- ------------------+------------------+---------------- ----- -------------------- + // [ Ldot ] [ dmDot/dPos = 0 ] [ dmDot/dVel = 0 ] [ dmDot/dm = 0 ] [ L ] [ dmDot/dParam = 0 ] + + // The J and K sub-columns and their derivatives (Jdot ...) are 3 elements columns, + // the L sub-colums and its derivative (Ldot) are 1 elements columns. + + // The expanded multiplication and addition above can be rewritten to take into + // account the fixed values found in the sub-matrices in the left factor. This leads to: + + // [ Jdot ] = [ K ] + // [ Kdot ] = [ dAcc/dPos ] * [ J ] + [ dAcc/dVel ] * [ K ] + [ dAcc/dm ] * [ L ] + [ dAcc/dParam ] + // [ Ldot ] = [ 0 ] + + // The following loops compute these expressions taking care of the mapping of the + // (J, K, L) columns into the single dimension array p and of the mapping of the + // (Jdot, Kdot, Ldot) columns into the single dimension array pDot. + + // copy K into Jdot + final int columnTop = stateDim * stateDim + k; + pDot[columnTop] = p[columnTop + 3 * paramDim]; + pDot[columnTop + paramDim] = p[columnTop + 4 * paramDim]; + pDot[columnTop + 2 * paramDim] = p[columnTop + 5 * paramDim]; + + // compute Kdot + for (int i = 0; i < dim; ++i) { + final double[] dAdPi = dAccdPos[i]; + final double[] dAdVi = dAccdVel[i]; + pDot[columnTop + (dim + i) * paramDim] = + dAccdParam[i] + + dAdPi[0] * p[columnTop] + dAdPi[1] * p[columnTop + paramDim] + dAdPi[2] * p[columnTop + 2 * paramDim] + + dAdVi[0] * p[columnTop + 3 * paramDim] + dAdVi[1] * p[columnTop + 4 * paramDim] + dAdVi[2] * p[columnTop + 5 * paramDim] + + ((dAccdM == null) ? 0.0 : dAccdM[i] * p[columnTop + 6 * paramDim]); + } + + if (dAccdM != null) { + // set Ldot to 0 + pDot[columnTop + 6 * paramDim] = 0; + } + + } + + } + +} + diff --git a/src/main/java/org/orekit/propagation/numerical/StateMapper.java b/src/main/java/org/orekit/propagation/numerical/StateMapper.java new file mode 100644 index 0000000000..9fd512351a --- /dev/null +++ b/src/main/java/org/orekit/propagation/numerical/StateMapper.java @@ -0,0 +1,59 @@ +/* Copyright 2002-2010 Centre National d'Études Spatiales + * Licensed to CS Communication & Systèmes (CS) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * CS licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.orekit.propagation.numerical; + +import java.io.Serializable; + +import org.orekit.attitudes.AttitudeLaw; +import org.orekit.errors.OrekitException; +import org.orekit.frames.Frame; +import org.orekit.propagation.SpacecraftState; +import org.orekit.time.AbsoluteDate; + +/** Interface defining mapping between simple state arrays and {@link SpacecraftState} instances. + * + * @see org.orekit.propagation.SpacecraftState + * @see org.orekit.propagation.numerical.NumericalPropagator + * @author Véronique Pommier-Maurussane + * @version $Revision$ $Date$ + */ +public interface StateMapper extends Serializable { + + /** Set the attitude law. + * @param attitudeLaw attitude law + */ + void setAttitudeLaw(final AttitudeLaw attitudeLaw); + + /** Convert spacecraft state to state array. + * @param s spacecraft state to map + * @param stateVector flat array into which the state vector should be mapped */ + void mapStateToArray(SpacecraftState s, double[] stateVector); + + /** Convert state array to space dynamics objects (AbsoluteDate and OrbitalParameters). + * @param array state as a flat array + * @param date integration date + * @param mu central attraction coefficient used for propagation (m<sup>3</sup>/s<sup>2</sup>) + * @param frame frame in which integration is performed + * @return state corresponding to the flat array as a space dynamics object + * @exception OrekitException if the attitude state cannot be determined + * by the attitude law + */ + SpacecraftState mapArrayToState(final double[] array, final AbsoluteDate date, + final double mu, final Frame frame) + throws OrekitException; + +} diff --git a/src/main/java/org/orekit/propagation/numerical/StateMapperCartesian.java b/src/main/java/org/orekit/propagation/numerical/StateMapperCartesian.java new file mode 100644 index 0000000000..b82365d923 --- /dev/null +++ b/src/main/java/org/orekit/propagation/numerical/StateMapperCartesian.java @@ -0,0 +1,87 @@ +/* Copyright 2002-2010 Centre National d'Études Spatiales + * Licensed to CS Communication & Systèmes (CS) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * CS licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.orekit.propagation.numerical; + +import org.apache.commons.math.geometry.Vector3D; +import org.orekit.attitudes.AttitudeLaw; +import org.orekit.errors.OrekitException; +import org.orekit.frames.Frame; +import org.orekit.orbits.CartesianOrbit; +import org.orekit.propagation.SpacecraftState; +import org.orekit.time.AbsoluteDate; +import org.orekit.utils.PVCoordinates; + +/** Implementation of the {@link StateMapper} interface for state arrays in cartesian coordinates. + * + * @see org.orekit.propagation.SpacecraftState + * @see org.orekit.propagation.numerical.NumericalPropagator + * @see TimeDerivativesEquationsCartesian + * @author Véronique Pommier-Maurussane + * @version $Revision$ $Date$ + */ +public class StateMapperCartesian implements StateMapper { + + /** Serializable UID. */ + private static final long serialVersionUID = -8228988658997240720L; + + /** Attitude law. */ + private AttitudeLaw attitudeLaw; + + /** Create a new instance. + */ + public StateMapperCartesian() { + } + + /** Set the attitude law. + * @param attitudeLaw attitude law + */ + public void setAttitudeLaw(final AttitudeLaw attitudeLaw) { + this.attitudeLaw = attitudeLaw; + } + + /** {@inheritDoc} */ + public void mapStateToArray(final SpacecraftState s, final double[] stateVector) { + + final PVCoordinates pv = s.getPVCoordinates(); + final Vector3D p = pv.getPosition(); + final Vector3D v = pv.getVelocity(); + + stateVector[0] = p.getX(); + stateVector[1] = p.getY(); + stateVector[2] = p.getZ(); + stateVector[3] = v.getX(); + stateVector[4] = v.getY(); + stateVector[5] = v.getZ(); + stateVector[6] = s.getMass(); + + } + + /** {@inheritDoc} */ + public SpacecraftState mapArrayToState(final double[] stateVector, final AbsoluteDate date, + final double mu, final Frame frame) + throws OrekitException { + + final Vector3D p = new Vector3D(stateVector[0], stateVector[1], stateVector[2]); + final Vector3D v = new Vector3D(stateVector[3], stateVector[4], stateVector[5]); + final PVCoordinates pv = new PVCoordinates(p, v); + final CartesianOrbit orbit = new CartesianOrbit(pv, frame, date, mu); + + return new SpacecraftState(orbit, attitudeLaw.getAttitude(orbit), stateVector[6]); + + } + +} diff --git a/src/main/java/org/orekit/propagation/numerical/StateMapperEquinoctial.java b/src/main/java/org/orekit/propagation/numerical/StateMapperEquinoctial.java new file mode 100644 index 0000000000..5095e68e08 --- /dev/null +++ b/src/main/java/org/orekit/propagation/numerical/StateMapperEquinoctial.java @@ -0,0 +1,74 @@ +/* Copyright 2002-2010 Centre National d'Études Spatiales + * Licensed to CS Communication & Systèmes (CS) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * CS licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.orekit.propagation.numerical; + +import org.orekit.attitudes.AttitudeLaw; +import org.orekit.errors.OrekitException; +import org.orekit.frames.Frame; +import org.orekit.orbits.EquinoctialOrbit; +import org.orekit.propagation.SpacecraftState; +import org.orekit.time.AbsoluteDate; + +/** Implementation of the {@link StateMapper} interface for state arrays in equinoctial parameters. +* +* @see org.orekit.propagation.SpacecraftState +* @see org.orekit.propagation.numerical.NumericalPropagator +* @see TimeDerivativesEquationsEquinoctial +* @author Véronique Pommier-Maurussane +* @version $Revision$ $Date$ +*/ +public class StateMapperEquinoctial implements StateMapper { + + /** Serializable UID. */ + private static final long serialVersionUID = 2882088208801391437L; + + /** Attitude law. */ + private AttitudeLaw attitudeLaw; + + /** Set the attitude law. + * @param attitudeLaw attitude law + */ + public void setAttitudeLaw(final AttitudeLaw attitudeLaw) { + this.attitudeLaw = attitudeLaw; + } + + /** {@inheritDoc} */ + public void mapStateToArray(final SpacecraftState s, final double[] stateVector) { + + stateVector[0] = s.getA(); + stateVector[1] = s.getEquinoctialEx(); + stateVector[2] = s.getEquinoctialEy(); + stateVector[3] = s.getHx(); + stateVector[4] = s.getHy(); + stateVector[5] = s.getLv(); + stateVector[6] = s.getMass(); + + } + + /** {@inheritDoc} */ + public SpacecraftState mapArrayToState(final double[] stateVector, final AbsoluteDate date, + final double mu, final Frame frame) throws OrekitException { + final EquinoctialOrbit orbit = + new EquinoctialOrbit(stateVector[0], stateVector[1], stateVector[2], stateVector[3], + stateVector[4], stateVector[5], EquinoctialOrbit.TRUE_LATITUDE_ARGUMENT, + frame, date, mu); + + return new SpacecraftState(orbit, attitudeLaw.getAttitude(orbit), stateVector[6]); + + } + +} diff --git a/src/main/java/org/orekit/propagation/numerical/TimeDerivativesEquations.java b/src/main/java/org/orekit/propagation/numerical/TimeDerivativesEquations.java index 8f95bd0a87..0a3c6a89e1 100644 --- a/src/main/java/org/orekit/propagation/numerical/TimeDerivativesEquations.java +++ b/src/main/java/org/orekit/propagation/numerical/TimeDerivativesEquations.java @@ -17,36 +17,32 @@ package org.orekit.propagation.numerical; import java.io.Serializable; -import java.util.Arrays; + import org.apache.commons.math.geometry.Vector3D; -import org.apache.commons.math.util.FastMath; import org.orekit.errors.OrekitException; import org.orekit.errors.OrekitMessages; import org.orekit.errors.PropagationException; import org.orekit.frames.Frame; -import org.orekit.frames.Transform; -import org.orekit.orbits.EquinoctialOrbit; -import org.orekit.utils.PVCoordinates; - +import org.orekit.orbits.Orbit; -/** This class sums up the contribution of several forces into orbit and mass derivatives. +/** Interface summing up the contribution of several forces into orbit and mass derivatives. * - * <p>The aim of this class is to gather the contributions of various perturbing + * <p>The aim of this interface is to gather the contributions of various perturbing * forces expressed as accelerations into one set of time-derivatives of - * {@link org.orekit.orbits.EquinoctialOrbit} plus one mass derivatives. - * It implements Gauss equations for the equinoctial parameters.</p> + * {@link org.orekit.orbits.Orbit} plus one mass derivatives. + * It implements Gauss equations for different kind of parameters.</p> * <p> * The state vector handled internally has the form that follows: * <pre> - * y[0] = a - * y[1] = ex - * y[2] = ey - * y[3] = hx - * y[4] = hy - * y[5] = lv - * y[6] = mass + * y[0] + * y[1] + * y[2] + * y[3] + * y[4] + * y[5] + * y[6] * </pre> - * where the six firsts paramters stands for the equinoctial parameters and the 7th + * where the six firsts parameters stands for the chosen parameters and the 7th * for the mass (kg) at the current time. * </p> * <p>The proper way to use this class is to have the object implementing the @@ -55,7 +51,7 @@ import org.orekit.utils.PVCoordinates; * <ul> * <li> * reinitialize the instance using the - * {@link #initDerivatives(double[], EquinoctialOrbit)} method + * {@link #initDerivatives(double[], Orbit)} method * </li> * <li> * pass the instance to each force model in turn so that they can put their @@ -67,233 +63,45 @@ import org.orekit.utils.PVCoordinates; * </li> * </ul> * </p> - * @see org.orekit.orbits.EquinoctialOrbit + * @see org.orekit.orbits.Orbit * @see org.orekit.propagation.numerical.NumericalPropagator * @author Luc Maisonobe * @author Fabien Maussion * @author Véronique Pommier-Maurussane * @version $Revision$ $Date$ */ -public class TimeDerivativesEquations implements Serializable { +public abstract class TimeDerivativesEquations implements Serializable { /** Serializable UID. */ - private static final long serialVersionUID = 1524631993375841290L; + private static final long serialVersionUID = 4183908052500627555L; - /** Orbital parameters. */ - private EquinoctialOrbit storedParameters; + // CHECKSTYLE: stop VisibilityModifierCheck /** Reference to the derivatives array to initialize. */ - private double[] storedYDot; - - /** First vector of the (q, s, w) local orbital frame. */ - private Vector3D lofQ; - - /** Second vector of the (q, s, w) local orbital frame. */ - private Vector3D lofS; - - /** First vector of the (t, n, w) local orbital frame. */ - private Vector3D lofT; - - /** Second vector of the (t, n, w) local orbital frame. */ - private Vector3D lofN; - - /** Third vector of both the (q, s, w) and (t, n, w) local orbital frames. */ - private Vector3D lofW; - - // CHECKSTYLE: stop JavadocVariable check + protected double[] storedYDot; - /** Multiplicative coefficients for the perturbing accelerations along lofQ. */ - private double aQ; - private double exQ; - private double eyQ; - - /** Multiplicative coefficients for the perturbing accelerations along lofS. */ - private double aS; - private double exS; - private double eyS; - - /** Multiplicative coefficients for the perturbing accelerations along lofT. */ - private double aT; - private double exT; - private double eyT; - - /** Multiplicative coefficients for the perturbing accelerations along lofN. */ - private double exN; - private double eyN; - - /** Multiplicative coefficients for the perturbing accelerations along lofW. */ - private double eyW; - private double exW; - private double hxW; - private double hyW; - private double lvW; - - // CHECKSTYLE: resume JavadocVariable check - - /** Kepler evolution on true latitude argument. */ - private double lvKepler; + //CHECKSTYLE: resume VisibilityModifierCheck /** Create a new instance. - * @param orbit current orbit parameters */ - protected TimeDerivativesEquations(final EquinoctialOrbit orbit) { - this.storedParameters = orbit; - lofQ = Vector3D.ZERO; - lofS = Vector3D.ZERO; - lofT = Vector3D.ZERO; - lofN = Vector3D.ZERO; - lofW = Vector3D.ZERO; - updateOrbitalFrames(); - } - - /** Update the orbital frames. */ - private void updateOrbitalFrames() { - - // get the position/velocity vectors - final PVCoordinates pvCoordinates = storedParameters.getPVCoordinates(); - - // compute orbital plane normal vector - lofW = pvCoordinates.getMomentum().normalize(); - - // compute (q, s, w) local orbital frame - lofQ = pvCoordinates.getPosition().normalize(); - lofS = Vector3D.crossProduct(lofW, lofQ); - - // compute (t, n, w) local orbital frame - lofT = pvCoordinates.getVelocity().normalize(); - lofN = Vector3D.crossProduct(lofW, lofT); - + protected TimeDerivativesEquations() { } /** Initialize all derivatives to zero. * @param yDot reference to the array where to put the derivatives. - * @param orbit current orbit parameters + * @param currentOrbit current orbit parameters * @exception PropagationException if the orbit evolve out of supported range */ - protected void initDerivatives(final double[] yDot, - final EquinoctialOrbit orbit) - throws PropagationException { - - - this.storedParameters = orbit; - updateOrbitalFrames(); - - // store derivatives array reference - this.storedYDot = yDot; - - // initialize derivatives to zero - Arrays.fill(storedYDot, 0.0); - - // intermediate variables - final double ex = storedParameters.getEquinoctialEx(); - final double ey = storedParameters.getEquinoctialEy(); - final double ex2 = ex * ex; - final double ey2 = ey * ey; - final double e2 = ex2 + ey2; - final double e = FastMath.sqrt(e2); - if (e >= 1) { - throw new PropagationException(OrekitMessages.ORBIT_BECOMES_HYPERBOLIC_UNABLE_TO_PROPAGATE_FURTHER, e); - } - - // intermediate variables - final double oMe2 = (1 - e) * (1 + e); - final double epsilon = FastMath.sqrt(oMe2); - final double a = storedParameters.getA(); - final double na = FastMath.sqrt(orbit.getMu() / a); - final double n = na / a; - final double lv = storedParameters.getLv(); - final double cLv = FastMath.cos(lv); - final double sLv = FastMath.sin(lv); - final double excLv = ex * cLv; - final double eysLv = ey * sLv; - final double excLvPeysLv = excLv + eysLv; - final double ksi = 1 + excLvPeysLv; - final double nu = ex * sLv - ey * cLv; - final double sqrt = FastMath.sqrt(ksi * ksi + nu * nu); - final double oPksi = 2 + excLvPeysLv; - final double hx = storedParameters.getHx(); - final double hy = storedParameters.getHy(); - final double h2 = hx * hx + hy * hy; - final double oPh2 = 1 + h2; - final double hxsLvMhycLv = hx * sLv - hy * cLv; - - final double epsilonOnNA = epsilon / na; - final double epsilonOnNAKsi = epsilonOnNA / ksi; - final double epsilonOnNAKsiSqrt = epsilonOnNAKsi / sqrt; - final double tOnEpsilonN = 2 / (n * epsilon); - final double tEpsilonOnNASqrt = 2 * epsilonOnNA / sqrt; - final double epsilonOnNAKsit = epsilonOnNA / (2 * ksi); - - // Kepler natural evolution - lvKepler = n * ksi * ksi / (oMe2 * epsilon); - - // coefficients along T - aT = tOnEpsilonN * sqrt; - exT = tEpsilonOnNASqrt * (ex + cLv); - eyT = tEpsilonOnNASqrt * (ey + sLv); - - // coefficients along N - exN = -epsilonOnNAKsiSqrt * (2 * ey * ksi + oMe2 * sLv); - eyN = epsilonOnNAKsiSqrt * (2 * ex * ksi + oMe2 * cLv); - - // coefficients along Q - aQ = tOnEpsilonN * nu; - exQ = epsilonOnNA * sLv; - eyQ = -epsilonOnNA * cLv; - - // coefficients along S - aS = tOnEpsilonN * ksi; - exS = epsilonOnNAKsi * (ex + oPksi * cLv); - eyS = epsilonOnNAKsi * (ey + oPksi * sLv); - - // coefficients along W - lvW = epsilonOnNAKsi * hxsLvMhycLv; - exW = -ey * lvW; - eyW = ex * lvW; - hxW = epsilonOnNAKsit * oPh2 * cLv; - hyW = epsilonOnNAKsit * oPh2 * sLv; - - } + abstract void initDerivatives(double[] yDot, Orbit currentOrbit) + throws PropagationException; /** Add the contribution of the Kepler evolution. * <p>Since the Kepler evolution is the most important, it should * be added after all the other ones, in order to improve * numerical accuracy.</p> + * @param mu central body gravitational constant */ - protected void addKeplerContribution() { - storedYDot[5] += lvKepler; - } - - /** Add the contribution of an acceleration expressed in (t, n, w) - * local orbital frame. - * @param t acceleration along the T axis (m/s<sup>2</sup>) - * @param n acceleration along the N axis (m/s<sup>2</sup>) - * @param w acceleration along the W axis (m/s<sup>2</sup>) - */ - public void addTNWAcceleration(final double t, final double n, final double w) { - storedYDot[0] += aT * t; - storedYDot[1] += exT * t + exN * n + exW * w; - storedYDot[2] += eyT * t + eyN * n + eyW * w; - storedYDot[3] += hxW * w; - storedYDot[4] += hyW * w; - storedYDot[5] += lvW * w; - } - - /** Add the contribution of an acceleration expressed in (q, s, w) - * local orbital frame. - * @param q acceleration along the Q axis (m/s<sup>2</sup>) - * @param s acceleration along the S axis (m/s<sup>2</sup>) - * @param w acceleration along the W axis (m/s<sup>2</sup>) - */ - public void addQSWAcceleration(final double q, final double s, final double w) { - storedYDot[0] += aQ * q + aS * s; - storedYDot[1] += exQ * q + exS * s + exW * w; - storedYDot[2] += eyQ * q + eyS * s + eyW * w; - storedYDot[3] += hxW * w; - storedYDot[4] += hyW * w; - storedYDot[5] += lvW * w; - } - + public abstract void addKeplerContribution(final double mu); /** Add the contribution of an acceleration expressed in the inertial frame * (it is important to make sure this acceleration is defined in the @@ -302,26 +110,14 @@ public class TimeDerivativesEquations implements Serializable { * @param y acceleration along the Y axis (m/s<sup>2</sup>) * @param z acceleration along the Z axis (m/s<sup>2</sup>) */ - public void addXYZAcceleration(final double x, final double y, final double z) { - addTNWAcceleration(x * lofT.getX() + y * lofT.getY() + z * lofT.getZ(), - x * lofN.getX() + y * lofN.getY() + z * lofN.getZ(), - x * lofW.getX() + y * lofW.getY() + z * lofW.getZ()); - } + public abstract void addXYZAcceleration(final double x, final double y, final double z); /** Add the contribution of an acceleration expressed in some inertial frame. * @param gamma acceleration vector (m/s<sup>2</sup>) * @param frame frame in which acceleration is defined (must be an inertial frame) * @exception OrekitException if frame transforms cannot be computed */ - public void addAcceleration(final Vector3D gamma, final Frame frame) - throws OrekitException { - final Transform t = frame.getTransformTo(storedParameters.getFrame(), - storedParameters.getDate()); - final Vector3D gammInRefFrame = t.transformVector(gamma); - addTNWAcceleration(Vector3D.dotProduct(gammInRefFrame, lofT), - Vector3D.dotProduct(gammInRefFrame, lofN), - Vector3D.dotProduct(gammInRefFrame, lofW)); - } + public abstract void addAcceleration(final Vector3D gamma, final Frame frame) throws OrekitException; /** Add the contribution of the flow rate (dm/dt). * @param q the flow rate, must be negative (dm/dt) @@ -334,37 +130,5 @@ public class TimeDerivativesEquations implements Serializable { storedYDot[6] += q; } - /** Get the first vector of the (q, s, w) local orbital frame. - * @return first vector of the (q, s, w) local orbital frame */ - public Vector3D getQ() { - return lofQ; - } - - /** Get the second vector of the (q, s, w) local orbital frame. - * @return second vector of the (q, s, w) local orbital frame */ - public Vector3D getS() { - return lofS; - } - - /** Get the first vector of the (t, n, w) local orbital frame. - * @return first vector of the (t, n, w) local orbital frame */ - public Vector3D getT() { - return lofT; - } - - /** Get the second vector of the (t, n, w) local orbital frame. - * @return second vector of the (t, n, w) local orbital frame */ - public Vector3D getN() { - return lofN; - } - - /** Get the third vector of both the (q, s, w) and (t, n, w) local orbital - * frames. - * @return third vector of both the (q, s, w) and (t, n, w) local orbital - * frames - */ - public Vector3D getW() { - return lofW; - } } diff --git a/src/main/java/org/orekit/propagation/numerical/TimeDerivativesEquationsCartesian.java b/src/main/java/org/orekit/propagation/numerical/TimeDerivativesEquationsCartesian.java new file mode 100644 index 0000000000..551a0b3dbe --- /dev/null +++ b/src/main/java/org/orekit/propagation/numerical/TimeDerivativesEquationsCartesian.java @@ -0,0 +1,114 @@ +/* Copyright 2002-2010 CS Centre National d'Études Spatiales + * Licensed to CS Communication & Systèmes (CS) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * CS licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.orekit.propagation.numerical; + +import java.util.Arrays; + +import org.apache.commons.math.geometry.Vector3D; +import org.apache.commons.math.util.FastMath; +import org.orekit.errors.OrekitException; +import org.orekit.errors.PropagationException; +import org.orekit.frames.Frame; +import org.orekit.frames.Transform; +import org.orekit.orbits.CartesianOrbit; +import org.orekit.orbits.Orbit; + +/** Implementation of the {@link TimeDerivativesEquations} interface for state arrays in cartesian coordinates +* +* <p>It implements Gauss equations for cartesian parameters. This implementation is specialized +* for state vectors that have the following form: +* <pre> +* y[0] = x +* y[1] = y +* y[2] = z +* y[3] = v<sub>x</sub> +* y[4] = v<sub>y</sub> +* y[5] = v<sub>z</sub> +* y[6] = mass +* </pre> +* where the six first parameters stands for the cartesian parameters and the 7<sup>th</sup> +* for the mass (kg) at the current time. +* </p> +* @see org.orekit.orbits.CartesianOrbit +* @see org.orekit.propagation.numerical.NumericalPropagator +* @author Luc Maisonobe +* @author Fabien Maussion +* @author Véronique Pommier-Maurussane +* @version $Revision$ $Date$ +*/ +public class TimeDerivativesEquationsCartesian extends TimeDerivativesEquations { + + /** Serializable UID. */ + private static final long serialVersionUID = -1882870530923825699L; + + /** Orbital parameters. */ + private CartesianOrbit storedParameters; + + /** Create a new instance. + * @param orbit current orbit parameters + */ + public TimeDerivativesEquationsCartesian(final CartesianOrbit orbit) { + this.storedParameters = orbit; + } + + /** {@inheritDoc} */ + public void addAcceleration(final Vector3D gamma, final Frame frame) + throws OrekitException { + final Transform t = frame.getTransformTo(storedParameters.getFrame(), + storedParameters.getDate()); + final Vector3D gammInRefFrame = t.transformVector(gamma); + addXYZAcceleration(gammInRefFrame.getX(), gammInRefFrame.getY(), gammInRefFrame.getZ()); + } + + /** {@inheritDoc} */ + public void addKeplerContribution(final double mu) { + final Vector3D position = storedParameters.getPVCoordinates().getPosition(); + final double r2 = position.getNormSq(); + final double coeff = -mu / (r2 * FastMath.sqrt(r2)); + storedYDot[3] += coeff * position.getX(); + storedYDot[4] += coeff * position.getY(); + storedYDot[5] += coeff * position.getZ(); + } + + /** {@inheritDoc} */ + public void addXYZAcceleration(final double x, final double y, final double z) { + storedYDot[3] += x; + storedYDot[4] += y; + storedYDot[5] += z; + } + + /** {@inheritDoc} */ + void initDerivatives(final double[] yDot, final Orbit orbit) + throws PropagationException { + + storedParameters = (CartesianOrbit) orbit; + + // store derivatives array reference + this.storedYDot = yDot; + + // initialize derivatives to zero + Arrays.fill(storedYDot, 0.0); + + // position derivative is velocity + final Vector3D velocity = storedParameters.getPVCoordinates().getVelocity(); + storedYDot[0] = velocity.getX(); + storedYDot[1] = velocity.getY(); + storedYDot[2] = velocity.getZ(); + + } + +} diff --git a/src/main/java/org/orekit/propagation/numerical/TimeDerivativesEquationsEquinoctial.java b/src/main/java/org/orekit/propagation/numerical/TimeDerivativesEquationsEquinoctial.java new file mode 100644 index 0000000000..c9c1d47550 --- /dev/null +++ b/src/main/java/org/orekit/propagation/numerical/TimeDerivativesEquationsEquinoctial.java @@ -0,0 +1,280 @@ +/* Copyright 2002-2010 CS Communication & Systèmes + * Licensed to CS Communication & Systèmes (CS) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * CS licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.orekit.propagation.numerical; + +import java.util.Arrays; + +import org.apache.commons.math.geometry.Vector3D; +import org.apache.commons.math.util.FastMath; +import org.orekit.errors.OrekitException; +import org.orekit.errors.OrekitMessages; +import org.orekit.errors.PropagationException; +import org.orekit.frames.Frame; +import org.orekit.frames.Transform; +import org.orekit.orbits.EquinoctialOrbit; +import org.orekit.orbits.Orbit; +import org.orekit.utils.PVCoordinates; + +/** Implementation of the {@link TimeDerivativesEquations} interface for state arrays in cartesian coordinates + * + * <p>It implements Gauss equations for equinoctial parameters. This implementation is specialized for + * state vectors that have the following form: + * <pre> + * y[0] = a + * y[1] = e<sub>x</sub> + * y[2] = e<sub>y</sub> + * y[3] = h<sub>x</sub> + * y[4] = h<sub>y</sub> + * y[5] = l<sub>v</sub> + * y[6] = mass + * </pre> + * where the six first parameters stands for the equinoctial parameters and the 7<sup>th</sup> + * for the mass (kg) at the current time. + * </p> + * @see org.orekit.orbits.EquinoctialOrbit + * @see org.orekit.propagation.numerical.NumericalPropagator + * @author Luc Maisonobe + * @author Fabien Maussion + * @author Véronique Pommier-Maurussane + * @version $Revision$ $Date$ + */ +public class TimeDerivativesEquationsEquinoctial extends TimeDerivativesEquations { + + /** Serializable UID. */ + private static final long serialVersionUID = -7676218190057010433L; + + /** First vector of the (q, s, w) local orbital frame. */ + private Vector3D lofQ; + + /** Second vector of the (q, s, w) local orbital frame. */ + private Vector3D lofS; + + /** First vector of the (t, n, w) local orbital frame. */ + private Vector3D lofT; + + /** Second vector of the (t, n, w) local orbital frame. */ + private Vector3D lofN; + + /** Third vector of both the (q, s, w) and (t, n, w) local orbital frames. */ + private Vector3D lofW; + + // CHECKSTYLE: stop JavadocVariable check + + /** Multiplicative coefficients for the perturbing accelerations along lofT. */ + private double aT; + private double exT; + private double eyT; + + /** Multiplicative coefficients for the perturbing accelerations along lofN. */ + private double exN; + private double eyN; + + /** Multiplicative coefficients for the perturbing accelerations along lofW. */ + private double eyW; + private double exW; + private double hxW; + private double hyW; + private double lvW; + + // CHECKSTYLE: resume JavadocVariable check + + /** Kepler evolution on true latitude argument. */ + private double lvKepler; + + /** Orbital parameters. */ + private EquinoctialOrbit storedParameters; + + /** Create a new instance. + * @param orbit current orbit parameters + */ + public TimeDerivativesEquationsEquinoctial(final EquinoctialOrbit orbit) { + this.storedParameters = orbit; + lofQ = Vector3D.ZERO; + lofS = Vector3D.ZERO; + lofT = Vector3D.ZERO; + lofN = Vector3D.ZERO; + lofW = Vector3D.ZERO; + updateOrbitalFrames(); + } + + /** Update the orbital frames. */ + private void updateOrbitalFrames() { + // get the position/velocity vectors + final PVCoordinates pvCoordinates = storedParameters.getPVCoordinates(); + + // compute orbital plane normal vector + lofW = pvCoordinates.getMomentum().normalize(); + + // compute (q, s, w) local orbital frame + lofQ = pvCoordinates.getPosition().normalize(); + lofS = Vector3D.crossProduct(lofW, lofQ); + + // compute (t, n, w) local orbital frame + lofT = pvCoordinates.getVelocity().normalize(); + lofN = Vector3D.crossProduct(lofW, lofT); + } + + + /** {@inheritDoc} */ + public void initDerivatives(final double[] yDot, final Orbit orbit) + throws PropagationException { + + + storedParameters = (EquinoctialOrbit) orbit; + updateOrbitalFrames(); + + // store derivatives array reference + this.storedYDot = yDot; + + // initialize derivatives to zero + Arrays.fill(storedYDot, 0.0); + + // intermediate variables + final double ex = storedParameters.getEquinoctialEx(); + final double ey = storedParameters.getEquinoctialEy(); + final double ex2 = ex * ex; + final double ey2 = ey * ey; + final double e2 = ex2 + ey2; + final double e = FastMath.sqrt(e2); + if (e >= 1) { + throw new PropagationException(OrekitMessages.ORBIT_BECOMES_HYPERBOLIC_UNABLE_TO_PROPAGATE_FURTHER, e); + } + + // intermediate variables + final double oMe2 = (1 - e) * (1 + e); + final double epsilon = FastMath.sqrt(oMe2); + final double a = storedParameters.getA(); + final double mu = orbit.getMu(); + final double na = FastMath.sqrt(mu / a); + final double n = na / a; + final double lv = storedParameters.getLv(); + final double cLv = FastMath.cos(lv); + final double sLv = FastMath.sin(lv); + final double excLv = ex * cLv; + final double eysLv = ey * sLv; + final double excLvPeysLv = excLv + eysLv; + final double ksi = 1 + excLvPeysLv; + final double nu = ex * sLv - ey * cLv; + final double sqrt = FastMath.sqrt(ksi * ksi + nu * nu); + final double hx = storedParameters.getHx(); + final double hy = storedParameters.getHy(); + final double h2 = hx * hx + hy * hy; + final double oPh2 = 1 + h2; + final double hxsLvMhycLv = hx * sLv - hy * cLv; + + final double epsilonOnNA = epsilon / na; + final double epsilonOnNAKsi = epsilonOnNA / ksi; + final double epsilonOnNAKsiSqrt = epsilonOnNAKsi / sqrt; + final double tOnEpsilonN = 2 / (n * epsilon); + final double tEpsilonOnNASqrt = 2 * epsilonOnNA / sqrt; + final double epsilonOnNAKsit = epsilonOnNA / (2 * ksi); + + // Kepler natural evolution without the mu part + lvKepler = n * ksi * ksi / (FastMath.sqrt(mu) * oMe2 * epsilon); + + // coefficients along T + aT = tOnEpsilonN * sqrt; + exT = tEpsilonOnNASqrt * (ex + cLv); + eyT = tEpsilonOnNASqrt * (ey + sLv); + + // coefficients along N + exN = -epsilonOnNAKsiSqrt * (2 * ey * ksi + oMe2 * sLv); + eyN = epsilonOnNAKsiSqrt * (2 * ex * ksi + oMe2 * cLv); + + // coefficients along W + lvW = epsilonOnNAKsi * hxsLvMhycLv; + exW = -ey * lvW; + eyW = ex * lvW; + hxW = epsilonOnNAKsit * oPh2 * cLv; + hyW = epsilonOnNAKsit * oPh2 * sLv; + + } + + /** {@inheritDoc} */ + public void addKeplerContribution(final double mu) { + storedYDot[5] += lvKepler * FastMath.sqrt(mu); + } + + /** Add the contribution of an acceleration expressed in (t, n, w) + * local orbital frame. + * @param t acceleration along the T axis (m/s<sup>2</sup>) + * @param n acceleration along the N axis (m/s<sup>2</sup>) + * @param w acceleration along the W axis (m/s<sup>2</sup>) + */ + private void addTNWAcceleration(final double t, final double n, final double w) { + storedYDot[0] += aT * t; + storedYDot[1] += exT * t + exN * n + exW * w; + storedYDot[2] += eyT * t + eyN * n + eyW * w; + storedYDot[3] += hxW * w; + storedYDot[4] += hyW * w; + storedYDot[5] += lvW * w; + } + + + /** {@inheritDoc} */ + public void addXYZAcceleration(final double x, final double y, final double z) { + addTNWAcceleration(x * lofT.getX() + y * lofT.getY() + z * lofT.getZ(), + x * lofN.getX() + y * lofN.getY() + z * lofN.getZ(), + x * lofW.getX() + y * lofW.getY() + z * lofW.getZ()); + } + + /** {@inheritDoc} */ + public void addAcceleration(final Vector3D gamma, final Frame frame) + throws OrekitException { + final Transform t = frame.getTransformTo(storedParameters.getFrame(), + storedParameters.getDate()); + final Vector3D gammInRefFrame = t.transformVector(gamma); + addTNWAcceleration(Vector3D.dotProduct(gammInRefFrame, lofT), + Vector3D.dotProduct(gammInRefFrame, lofN), + Vector3D.dotProduct(gammInRefFrame, lofW)); + } + + + /** Get the first vector of the (q, s, w) local orbital frame. + * @return first vector of the (q, s, w) local orbital frame */ + public Vector3D getQ() { + return lofQ; + } + + /** Get the second vector of the (q, s, w) local orbital frame. + * @return second vector of the (q, s, w) local orbital frame */ + public Vector3D getS() { + return lofS; + } + + /** Get the first vector of the (t, n, w) local orbital frame. + * @return first vector of the (t, n, w) local orbital frame */ + public Vector3D getT() { + return lofT; + } + + /** Get the second vector of the (t, n, w) local orbital frame. + * @return second vector of the (t, n, w) local orbital frame */ + public Vector3D getN() { + return lofN; + } + + /** Get the third vector of both the (q, s, w) and (t, n, w) local orbital + * frames. + * @return third vector of both the (q, s, w) and (t, n, w) local orbital + * frames + */ + public Vector3D getW() { + return lofW; + } + +} diff --git a/src/main/java/org/orekit/propagation/numerical/TimeDerivativesEquationsWithJacobians.java b/src/main/java/org/orekit/propagation/numerical/TimeDerivativesEquationsWithJacobians.java deleted file mode 100644 index a78c6cbb85..0000000000 --- a/src/main/java/org/orekit/propagation/numerical/TimeDerivativesEquationsWithJacobians.java +++ /dev/null @@ -1,90 +0,0 @@ -/* Copyright 2002-2010 CS Communication & Systèmes - * Licensed to CS Communication & Systèmes (CS) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * CS licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.orekit.propagation.numerical; - -import java.util.Arrays; - -import org.orekit.errors.PropagationException; -import org.orekit.orbits.EquinoctialOrbit; - - -/** This class sums up the contribution of several forces into - * orbit and mass derivatives and partial derivatives. - * <p> - * As of 5.0, this class is still considered experimental, so use it with care. - * </p> - * <p> - * It is related to {@link NumericalPropagatorWithJacobians} like - * {@link TimeDerivativesEquations} to {@link NumericalPropagator}. - * </p> - * - * @see TimeDerivativesEquations - * @see NumericalPropagator - * @see NumericalPropagatorWithJacobians - * - * @author Pascal Parraud - * @version $Revision$ $Date$ - */ -public class TimeDerivativesEquationsWithJacobians extends TimeDerivativesEquations { - - /** Serializable UID. */ - private static final long serialVersionUID = -1378236382967204061L; - - /** Reference to the orbital parameters jacobian array to initialize. */ - private double[][] storedDFDY; - - /** Reference to the force models parameters jacobian array to initialize. */ - private double[][] storedDFDP; - - /** Create a new instance. - * @param orbit current orbit parameters - */ - protected TimeDerivativesEquationsWithJacobians(final EquinoctialOrbit orbit) { - super(orbit); - } - - /** Initialize all derivatives and jacobians to zero. - * @param yDot reference to the array where to put the derivatives. - * @param dFdY placeholder array where to put the jacobian of the ODE with respect to the state vector - * @param dFdP placeholder array where to put the jacobian of the ODE with respect to the parameters - * @param orbit current orbit parameters - * @exception PropagationException if the orbit evolve out of supported range - */ - protected void initDerivativesWithJacobians(final double[] yDot, - final double[][] dFdY, - final double[][] dFdP, - final EquinoctialOrbit orbit) - throws PropagationException { - - initDerivatives(yDot, orbit); - - // store jacobians reference - this.storedDFDY = dFdY; - this.storedDFDP = dFdP; - - // initialize jacobians to zero - for (final double[] row : storedDFDY) { - Arrays.fill(row, 0.0); - } - - for (final double[] row : storedDFDP) { - Arrays.fill(row, 0.0); - } - - } - -} diff --git a/src/main/java/org/orekit/propagation/package.html b/src/main/java/org/orekit/propagation/package.html index 11ff5daa25..ffc19ef73c 100644 --- a/src/main/java/org/orekit/propagation/package.html +++ b/src/main/java/org/orekit/propagation/package.html @@ -57,10 +57,6 @@ This package provides tools to propagate orbital states with different methods. ask for the next first time derivative, etc. until it reaches the final asked date. </p> -<p> The {@link org.orekit.propagation.numerical.NumericalPropagatorWithJacobians} - is a specialized class which enables to compute jacobians with respect to orbital - parameters and force models parameters while propagating. -</p> @author Luc Maisonobe @author Fabien Maussion diff --git a/src/main/java/org/orekit/propagation/precomputed/IntegratedEphemeris.java b/src/main/java/org/orekit/propagation/precomputed/IntegratedEphemeris.java index 0fab821dbf..f3080379f5 100644 --- a/src/main/java/org/orekit/propagation/precomputed/IntegratedEphemeris.java +++ b/src/main/java/org/orekit/propagation/precomputed/IntegratedEphemeris.java @@ -16,19 +16,21 @@ */ package org.orekit.propagation.precomputed; +import java.util.List; + import org.apache.commons.math.exception.MathUserException; import org.apache.commons.math.ode.ContinuousOutputModel; import org.apache.commons.math.ode.sampling.StepHandler; import org.apache.commons.math.ode.sampling.StepInterpolator; -import org.orekit.attitudes.AttitudeLaw; import org.orekit.errors.OrekitException; import org.orekit.errors.OrekitMessages; import org.orekit.errors.PropagationException; import org.orekit.frames.Frame; -import org.orekit.orbits.EquinoctialOrbit; import org.orekit.propagation.BoundedPropagator; import org.orekit.propagation.SpacecraftState; +import org.orekit.propagation.numerical.AdditionalStateAndEquations; import org.orekit.propagation.numerical.ModeHandler; +import org.orekit.propagation.numerical.StateMapper; import org.orekit.time.AbsoluteDate; import org.orekit.utils.PVCoordinates; @@ -70,7 +72,10 @@ public class IntegratedEphemeris implements BoundedPropagator, ModeHandler, StepHandler { /** Serializable UID. */ - private static final long serialVersionUID = 3997196481383054719L; + private static final long serialVersionUID = -7217642644695032540L; + + /** Mapper between spacecraft state and simple array. */ + private StateMapper mapper; /** Reference date. */ private AbsoluteDate initializedReference; @@ -81,9 +86,6 @@ public class IntegratedEphemeris /** Central body gravitational constant. */ private double initializedMu; - /** Attitude law. */ - private AttitudeLaw initializedAttitudeLaw; - /** Start date of the integration (can be min or max). */ private AbsoluteDate startDate; @@ -104,13 +106,12 @@ public class IntegratedEphemeris } /** {@inheritDoc} */ - public void initialize(final AbsoluteDate reference, - final Frame frame, final double mu, - final AttitudeLaw attitudeLaw) { - this.initializedReference = reference; - this.initializedFrame = frame; - this.initializedMu = mu; - this.initializedAttitudeLaw = attitudeLaw; + public void initialize(final StateMapper mapper, final List <AdditionalStateAndEquations> addStateAndEqu, + final AbsoluteDate reference, final Frame frame, final double mu) { + this.mapper = mapper; + this.initializedReference = reference; + this.initializedFrame = frame; + this.initializedMu = mu; // dates will be set when last step is handled startDate = null; @@ -128,18 +129,12 @@ public class IntegratedEphemeris date, minDate, maxDate); } model.setInterpolatedTime(date.durationFrom(startDate)); - final double[] state = model.getInterpolatedState(); - - final EquinoctialOrbit eq = - new EquinoctialOrbit(state[0], state[1], state[2], - state[3], state[4], state[5], 2, initializedFrame, date, initializedMu); - final double mass = state[6]; - - return new SpacecraftState(eq, initializedAttitudeLaw.getAttitude(eq), mass); - } catch (OrekitException oe) { - throw new PropagationException(oe); + return mapper.mapArrayToState(model.getInterpolatedState(), date, + initializedMu, initializedFrame); } catch (MathUserException mue) { throw new PropagationException(mue, mue.getGeneralPattern(), mue.getArguments()); + } catch (OrekitException oe) { + throw new PropagationException(oe); } } @@ -164,13 +159,14 @@ public class IntegratedEphemeris } /** {@inheritDoc} */ - public void handleStep(final StepInterpolator interpolator, - final boolean isLast) { + public void handleStep(final StepInterpolator interpolator, final boolean isLast) { model.handleStep(interpolator, isLast); if (isLast) { - startDate = initializedReference.shiftedBy(model.getInitialTime()); - maxDate = initializedReference.shiftedBy(model.getFinalTime()); - if (maxDate.durationFrom(startDate) < 0) { + final double tI = model.getInitialTime(); + final double tF = model.getFinalTime(); + startDate = initializedReference.shiftedBy(tI); + maxDate = initializedReference.shiftedBy(tF); + if (tF < tI) { minDate = maxDate; maxDate = startDate; } else { diff --git a/src/main/java/org/orekit/propagation/sampling/AdaptedStepHandler.java b/src/main/java/org/orekit/propagation/sampling/AdaptedStepHandler.java index 5b8776eb35..8e8060ba92 100644 --- a/src/main/java/org/orekit/propagation/sampling/AdaptedStepHandler.java +++ b/src/main/java/org/orekit/propagation/sampling/AdaptedStepHandler.java @@ -17,18 +17,20 @@ package org.orekit.propagation.sampling; import java.io.Serializable; +import java.util.List; import org.apache.commons.math.exception.MathUserException; import org.apache.commons.math.ode.sampling.StepHandler; import org.apache.commons.math.ode.sampling.StepInterpolator; -import org.orekit.attitudes.AttitudeLaw; import org.orekit.errors.OrekitException; +import org.orekit.errors.OrekitMessages; import org.orekit.errors.PropagationException; import org.orekit.frames.Frame; -import org.orekit.orbits.EquinoctialOrbit; -import org.orekit.orbits.Orbit; import org.orekit.propagation.SpacecraftState; +import org.orekit.propagation.numerical.AdditionalEquations; +import org.orekit.propagation.numerical.AdditionalStateAndEquations; import org.orekit.propagation.numerical.ModeHandler; +import org.orekit.propagation.numerical.StateMapper; import org.orekit.time.AbsoluteDate; /** Adapt an {@link org.orekit.propagation.sampling.OrekitStepHandler} @@ -42,6 +44,12 @@ public class AdaptedStepHandler /** Serializable UID. */ private static final long serialVersionUID = -8067262257341902186L; + /** Mapper between spacecraft state and simple array. */ + private StateMapper mapper; + + /** Additional state and equations list. */ + private List <AdditionalStateAndEquations> addStateAndEqu; + /** Reference date. */ private AbsoluteDate initializedReference; @@ -51,9 +59,6 @@ public class AdaptedStepHandler /** Central body attraction coefficient. */ private double initializedMu; - /** Attitude law. */ - private AttitudeLaw initializedAttitudeLaw; - /** Underlying handler. */ private final OrekitStepHandler handler; @@ -68,11 +73,12 @@ public class AdaptedStepHandler } /** {@inheritDoc} */ - public void initialize(final AbsoluteDate reference, final Frame frame, - final double mu, final AttitudeLaw attitudeLaw) { + public void initialize(final StateMapper stateMapper, final List <AdditionalStateAndEquations> stateAndEqu, + final AbsoluteDate reference, final Frame frame, final double mu) { + this.mapper = stateMapper; + this.addStateAndEqu = stateAndEqu; this.initializedReference = reference; this.initializedFrame = frame; - this.initializedAttitudeLaw = attitudeLaw; this.initializedMu = mu; } @@ -144,16 +150,46 @@ public class AdaptedStepHandler try { final double[] y = rawInterpolator.getInterpolatedState(); final AbsoluteDate interpolatedDate = initializedReference.shiftedBy(rawInterpolator.getInterpolatedTime()); - final Orbit orbit = - new EquinoctialOrbit(y[0], y[1], y[2], y[3], y[4], y[5], - EquinoctialOrbit.TRUE_LATITUDE_ARGUMENT, - initializedFrame, interpolatedDate, initializedMu); - return new SpacecraftState(orbit, initializedAttitudeLaw.getAttitude(orbit), y[6]); + return mapper.mapArrayToState(y, interpolatedDate, initializedMu, initializedFrame); } catch (MathUserException mue) { throw new PropagationException(mue, mue.getGeneralPattern(), mue.getArguments()); } } + /** Get the interpolated additional state corresponding to the additional equations. + * @param addEqu additional equation used as a reference for selection + * @return interpolated additional state at the current interpolation date + * @exception OrekitException if state cannot be interpolated or converted + * @see #getInterpolatedDate() + * @see #setInterpolatedDate(AbsoluteDate) + */ + public double[] getInterpolatedAdditionalState(final AdditionalEquations addEqu) + throws OrekitException { + try { + + // propagate the whole state vector + final double[] y = rawInterpolator.getInterpolatedState(); + + // get portion of additional state to update + int index = 7; + for (final AdditionalStateAndEquations stateAndEqu : addStateAndEqu) { + if (stateAndEqu.getAdditionalEquations() == addEqu) { + final double[] state = stateAndEqu.getAdditionalState(); + System.arraycopy(y, index, state, 0, state.length); + return state; + } + // incrementing index + index += stateAndEqu.getAdditionalState().length; + } + + throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_EQUATION); + + } catch (MathUserException mue) { + throw new PropagationException(mue, mue.getGeneralPattern(), mue.getArguments()); + } + + } + /** Check is integration direction is forward in date. * @return true if integration is forward in date */ diff --git a/src/main/java/org/orekit/propagation/sampling/BasicStepInterpolator.java b/src/main/java/org/orekit/propagation/sampling/BasicStepInterpolator.java index c0a3bc696e..28ab851e32 100644 --- a/src/main/java/org/orekit/propagation/sampling/BasicStepInterpolator.java +++ b/src/main/java/org/orekit/propagation/sampling/BasicStepInterpolator.java @@ -20,6 +20,7 @@ import org.orekit.errors.OrekitException; import org.orekit.errors.PropagationException; import org.orekit.propagation.BasicPropagator; import org.orekit.propagation.SpacecraftState; +import org.orekit.propagation.numerical.AdditionalEquations; import org.orekit.time.AbsoluteDate; /** Implementation of the {@link OrekitStepInterpolator} interface based @@ -87,6 +88,13 @@ public class BasicStepInterpolator implements OrekitStepInterpolator { interpolatedState = propagator.propagate(date); } + /** {@inheritDoc} */ + public double[] getInterpolatedAdditionalState(final AdditionalEquations addEqu) + throws OrekitException { + // This should never happen + throw OrekitException.createInternalError(null); + } + /** Shift one step forward. * Copy the current date into the previous date, hence preparing the * interpolator for future calls to {@link #storeDate storeDate} @@ -106,4 +114,5 @@ public class BasicStepInterpolator implements OrekitStepInterpolator { setInterpolatedDate(currentDate); } + } diff --git a/src/main/java/org/orekit/propagation/sampling/OrekitStepInterpolator.java b/src/main/java/org/orekit/propagation/sampling/OrekitStepInterpolator.java index 50e9cdf62a..96bb206c7c 100644 --- a/src/main/java/org/orekit/propagation/sampling/OrekitStepInterpolator.java +++ b/src/main/java/org/orekit/propagation/sampling/OrekitStepInterpolator.java @@ -21,6 +21,7 @@ import java.io.Serializable; import org.orekit.errors.OrekitException; import org.orekit.errors.PropagationException; import org.orekit.propagation.SpacecraftState; +import org.orekit.propagation.numerical.AdditionalEquations; import org.orekit.time.AbsoluteDate; /** This interface is a space-dynamics aware step interpolator. @@ -73,6 +74,15 @@ public interface OrekitStepInterpolator extends Serializable { */ SpacecraftState getInterpolatedState() throws OrekitException; + /** Get the interpolated additional state corresponding to the additional equations. + * @param addEqu additional equation used as a reference for selection + * @return interpolated additional state at the current interpolation date + * @exception OrekitException if state cannot be interpolated or converted + * @see #getInterpolatedDate() + * @see #setInterpolatedDate(AbsoluteDate) + */ + double[] getInterpolatedAdditionalState(AdditionalEquations addEqu) throws OrekitException; + /** Check is integration direction is forward in date. * @return true if integration is forward in date */ diff --git a/src/main/resources/META-INF/localization/OrekitMessages_de.properties b/src/main/resources/META-INF/localization/OrekitMessages_de.properties index e159329962..dd340dd297 100644 --- a/src/main/resources/META-INF/localization/OrekitMessages_de.properties +++ b/src/main/resources/META-INF/localization/OrekitMessages_de.properties @@ -220,6 +220,30 @@ UNEXPECTED_TWO_ELEVATION_VALUES_FOR_ONE_AZIMUTH = <MISSING TRANSLATION!> # unknown parameter {0} UNKNOWN_PARAMETER = unbekannter Parameter {0} +# unsupported parameter name {0}: supported names {1}, {2} +UNSUPPORTED_PARAMETER_1_2 = <MISSING TRANSLATION!> + +# unknown additional equation +UNKNOWN_ADDITIONAL_EQUATION = <MISSING TRANSLATION!> + +# partial derivatives can be propagated only in cartesian parameters type +PARTIAL_DERIVATIVES_ONLY_IN_CARTESIAN = <MISSING TRANSLATION!> + +# state jacobian is a {0}x{1} matrix, it should be either a 6x6 or a 7x7 matrix +STATE_JACOBIAN_NEITHER_6X6_NOR_7X7 = <MISSING TRANSLATION!> + +# state jacobian has {0} rows but parameters jacobian has {1} rows +STATE_AND_PARAMETERS_JACOBIANS_ROWS_MISMATCH = <MISSING TRANSLATION!> + +# initial jacobian matrix has {0} columns, but {1} parameters have been selected +INITIAL_MATRIX_AND_PARAMETERS_NUMBER_MISMATCH = <MISSING TRANSLATION!> + +# orbit should be either elliptic with a > 0 and e < 1 or hyperbolic with a < 0 and e > 1, a = {0}, e = {1} +ORBIT_A_E_MISMATCH_WITH_CONIC_TYPE = <MISSING TRANSLATION!> + +# hyperbolic orbits cannot be handled as {0} instances +HYPERBOLIC_ORBIT_NOT_HANDLED_AS = <MISSING TRANSLATION!> + # invalid preamble field in CCSDS date: {0} CCSDS_DATE_INVALID_PREAMBLE_FIELD = <MISSING TRANSLATION!> diff --git a/src/main/resources/META-INF/localization/OrekitMessages_en.properties b/src/main/resources/META-INF/localization/OrekitMessages_en.properties index c561165411..8c0ea681cb 100644 --- a/src/main/resources/META-INF/localization/OrekitMessages_en.properties +++ b/src/main/resources/META-INF/localization/OrekitMessages_en.properties @@ -220,6 +220,30 @@ UNEXPECTED_TWO_ELEVATION_VALUES_FOR_ONE_AZIMUTH = unexpected two elevation value # unknown parameter {0} UNKNOWN_PARAMETER = unknown parameter {0} +# unsupported parameter name {0}: supported names {1}, {2} +UNSUPPORTED_PARAMETER_1_2 = unsupported parameter name {0}: supported names {1}, {2} + +# unknown additional equation +UNKNOWN_ADDITIONAL_EQUATION = unknown additional equation + +# partial derivatives can be propagated only in cartesian parameters type +PARTIAL_DERIVATIVES_ONLY_IN_CARTESIAN = partial derivatives can be propagated only in cartesian parameters type + +# state jacobian is a {0}x{1} matrix, it should be either a 6x6 or a 7x7 matrix +STATE_JACOBIAN_NEITHER_6X6_NOR_7X7 = state jacobian is a {0}x{1} matrix, it should be either a 6x6 or a 7x7 matrix + +# state jacobian has {0} rows but parameters jacobian has {1} rows +STATE_AND_PARAMETERS_JACOBIANS_ROWS_MISMATCH = state jacobian has {0} rows but parameters jacobian has {1} rows + +# initial jacobian matrix has {0} columns, but {1} parameters have been selected +INITIAL_MATRIX_AND_PARAMETERS_NUMBER_MISMATCH = initial jacobian matrix has {0} columns, but {1} parameters have been selected + +# orbit should be either elliptic with a > 0 and e < 1 or hyperbolic with a < 0 and e > 1, a = {0}, e = {1} +ORBIT_A_E_MISMATCH_WITH_CONIC_TYPE = orbit should be either elliptic with a > 0 and e < 1 or hyperbolic with a < 0 and e > 1, a = {0}, e = {1} + +# hyperbolic orbits cannot be handled as {0} instances +HYPERBOLIC_ORBIT_NOT_HANDLED_AS = hyperbolic orbits cannot be handled as {0} instances + # invalid preamble field in CCSDS date: {0} CCSDS_DATE_INVALID_PREAMBLE_FIELD = invalid preamble field in CCSDS date: {0} diff --git a/src/main/resources/META-INF/localization/OrekitMessages_es.properties b/src/main/resources/META-INF/localization/OrekitMessages_es.properties index 03c77a7acd..d00979f6c1 100644 --- a/src/main/resources/META-INF/localization/OrekitMessages_es.properties +++ b/src/main/resources/META-INF/localization/OrekitMessages_es.properties @@ -220,6 +220,30 @@ UNEXPECTED_TWO_ELEVATION_VALUES_FOR_ONE_AZIMUTH = <MISSING TRANSLATION!> # unknown parameter {0} UNKNOWN_PARAMETER = par\u00e1metro {0} desconocido +# unsupported parameter name {0}: supported names {1}, {2} +UNSUPPORTED_PARAMETER_1_2 = <MISSING TRANSLATION!> + +# unknown additional equation +UNKNOWN_ADDITIONAL_EQUATION = <MISSING TRANSLATION!> + +# partial derivatives can be propagated only in cartesian parameters type +PARTIAL_DERIVATIVES_ONLY_IN_CARTESIAN = <MISSING TRANSLATION!> + +# state jacobian is a {0}x{1} matrix, it should be either a 6x6 or a 7x7 matrix +STATE_JACOBIAN_NEITHER_6X6_NOR_7X7 = <MISSING TRANSLATION!> + +# state jacobian has {0} rows but parameters jacobian has {1} rows +STATE_AND_PARAMETERS_JACOBIANS_ROWS_MISMATCH = <MISSING TRANSLATION!> + +# initial jacobian matrix has {0} columns, but {1} parameters have been selected +INITIAL_MATRIX_AND_PARAMETERS_NUMBER_MISMATCH = <MISSING TRANSLATION!> + +# orbit should be either elliptic with a > 0 and e < 1 or hyperbolic with a < 0 and e > 1, a = {0}, e = {1} +ORBIT_A_E_MISMATCH_WITH_CONIC_TYPE = <MISSING TRANSLATION!> + +# hyperbolic orbits cannot be handled as {0} instances +HYPERBOLIC_ORBIT_NOT_HANDLED_AS = <MISSING TRANSLATION!> + # invalid preamble field in CCSDS date: {0} CCSDS_DATE_INVALID_PREAMBLE_FIELD = <MISSING TRANSLATION!> diff --git a/src/main/resources/META-INF/localization/OrekitMessages_fr.properties b/src/main/resources/META-INF/localization/OrekitMessages_fr.properties index 704d41597d..0b8e7717e7 100644 --- a/src/main/resources/META-INF/localization/OrekitMessages_fr.properties +++ b/src/main/resources/META-INF/localization/OrekitMessages_fr.properties @@ -220,6 +220,30 @@ UNEXPECTED_TWO_ELEVATION_VALUES_FOR_ONE_AZIMUTH = donn\u00e9es inattendues, deux # unknown parameter {0} UNKNOWN_PARAMETER = param\u00e8tre {0} inconnu +# unsupported parameter name {0}: supported names {1}, {2} +UNSUPPORTED_PARAMETER_1_2 = param\u00e8tre {0} inconnu : param\u00e8tres connus {1}, {2} + +# unknown additional equation +UNKNOWN_ADDITIONAL_EQUATION = \u00e9quation additionelle inconnue + +# partial derivatives can be propagated only in cartesian parameters type +PARTIAL_DERIVATIVES_ONLY_IN_CARTESIAN = les d\u00e9riv\u00e9es partielles ne peuvent \u00eatre propag\u00e9es qu''en param\u00e8tres cart\u00e9siens + +# state jacobian is a {0}x{1} matrix, it should be either a 6x6 or a 7x7 matrix +STATE_JACOBIAN_NEITHER_6X6_NOR_7X7 = la jacobienne de l''\u00e9tat est une matrice {0}x{1}, alors qu''elle devrait \u00eatre soit une matrice 6x6 soit une matrice 7x7 + +# state jacobian has {0} rows but parameters jacobian has {1} rows +STATE_AND_PARAMETERS_JACOBIANS_ROWS_MISMATCH = la jacobienne de l''\u00e9tat a {0} lignes alors que la jacobienne des param\u00e8tres en a {1} + +# initial jacobian matrix has {0} columns, but {1} parameters have been selected +INITIAL_MATRIX_AND_PARAMETERS_NUMBER_MISMATCH = la matrice initiale a {0} colonnes alors que {1} param\u00e8tres ont \u00e9t\u00e9 s\u00e9lectionn\u00e9s + +# orbit should be either elliptic with a > 0 and e < 1 or hyperbolic with a < 0 and e > 1, a = {0}, e = {1} +ORBIT_A_E_MISMATCH_WITH_CONIC_TYPE = l''orbite devrait \u00eatre soit elliptique avec a > 0 et e < 1 ou hyperbolique avec a < 0 et e > 1, a = {0}, e = {1} + +# hyperbolic orbits cannot be handled as {0} instances +HYPERBOLIC_ORBIT_NOT_HANDLED_AS = les orbites hyperboliques ne peuvent pas \u00eatre des instances de {0} + # invalid preamble field in CCSDS date: {0} CCSDS_DATE_INVALID_PREAMBLE_FIELD = pr\u00e9ambule invalide dans une date CCSDS : {0} diff --git a/src/main/resources/META-INF/localization/OrekitMessages_gl.properties b/src/main/resources/META-INF/localization/OrekitMessages_gl.properties index 6fe471ccf3..3c706a2f75 100644 --- a/src/main/resources/META-INF/localization/OrekitMessages_gl.properties +++ b/src/main/resources/META-INF/localization/OrekitMessages_gl.properties @@ -220,6 +220,30 @@ UNEXPECTED_TWO_ELEVATION_VALUES_FOR_ONE_AZIMUTH = <MISSING TRANSLATION!> # unknown parameter {0} UNKNOWN_PARAMETER = par\u00e1metro {0} desco\u00f1ecido +# unsupported parameter name {0}: supported names {1}, {2} +UNSUPPORTED_PARAMETER_1_2 = <MISSING TRANSLATION!> + +# unknown additional equation +UNKNOWN_ADDITIONAL_EQUATION = <MISSING TRANSLATION!> + +# partial derivatives can be propagated only in cartesian parameters type +PARTIAL_DERIVATIVES_ONLY_IN_CARTESIAN = <MISSING TRANSLATION!> + +# state jacobian is a {0}x{1} matrix, it should be either a 6x6 or a 7x7 matrix +STATE_JACOBIAN_NEITHER_6X6_NOR_7X7 = <MISSING TRANSLATION!> + +# state jacobian has {0} rows but parameters jacobian has {1} rows +STATE_AND_PARAMETERS_JACOBIANS_ROWS_MISMATCH = <MISSING TRANSLATION!> + +# initial jacobian matrix has {0} columns, but {1} parameters have been selected +INITIAL_MATRIX_AND_PARAMETERS_NUMBER_MISMATCH = <MISSING TRANSLATION!> + +# orbit should be either elliptic with a > 0 and e < 1 or hyperbolic with a < 0 and e > 1, a = {0}, e = {1} +ORBIT_A_E_MISMATCH_WITH_CONIC_TYPE = <MISSING TRANSLATION!> + +# hyperbolic orbits cannot be handled as {0} instances +HYPERBOLIC_ORBIT_NOT_HANDLED_AS = <MISSING TRANSLATION!> + # invalid preamble field in CCSDS date: {0} CCSDS_DATE_INVALID_PREAMBLE_FIELD = <MISSING TRANSLATION!> diff --git a/src/main/resources/META-INF/localization/OrekitMessages_it.properties b/src/main/resources/META-INF/localization/OrekitMessages_it.properties index 0988870357..55bf929bf6 100644 --- a/src/main/resources/META-INF/localization/OrekitMessages_it.properties +++ b/src/main/resources/META-INF/localization/OrekitMessages_it.properties @@ -220,6 +220,30 @@ UNEXPECTED_TWO_ELEVATION_VALUES_FOR_ONE_AZIMUTH = <MISSING TRANSLATION!> # unknown parameter {0} UNKNOWN_PARAMETER = parametro {0} sconosciuto +# unsupported parameter name {0}: supported names {1}, {2} +UNSUPPORTED_PARAMETER_1_2 = <MISSING TRANSLATION!> + +# unknown additional equation +UNKNOWN_ADDITIONAL_EQUATION = <MISSING TRANSLATION!> + +# partial derivatives can be propagated only in cartesian parameters type +PARTIAL_DERIVATIVES_ONLY_IN_CARTESIAN = <MISSING TRANSLATION!> + +# state jacobian is a {0}x{1} matrix, it should be either a 6x6 or a 7x7 matrix +STATE_JACOBIAN_NEITHER_6X6_NOR_7X7 = <MISSING TRANSLATION!> + +# state jacobian has {0} rows but parameters jacobian has {1} rows +STATE_AND_PARAMETERS_JACOBIANS_ROWS_MISMATCH = <MISSING TRANSLATION!> + +# initial jacobian matrix has {0} columns, but {1} parameters have been selected +INITIAL_MATRIX_AND_PARAMETERS_NUMBER_MISMATCH = <MISSING TRANSLATION!> + +# orbit should be either elliptic with a > 0 and e < 1 or hyperbolic with a < 0 and e > 1, a = {0}, e = {1} +ORBIT_A_E_MISMATCH_WITH_CONIC_TYPE = <MISSING TRANSLATION!> + +# hyperbolic orbits cannot be handled as {0} instances +HYPERBOLIC_ORBIT_NOT_HANDLED_AS = <MISSING TRANSLATION!> + # invalid preamble field in CCSDS date: {0} CCSDS_DATE_INVALID_PREAMBLE_FIELD = <MISSING TRANSLATION!> diff --git a/src/main/resources/META-INF/localization/OrekitMessages_no.properties b/src/main/resources/META-INF/localization/OrekitMessages_no.properties index c44d3679ef..1e045e0524 100644 --- a/src/main/resources/META-INF/localization/OrekitMessages_no.properties +++ b/src/main/resources/META-INF/localization/OrekitMessages_no.properties @@ -220,6 +220,30 @@ UNEXPECTED_TWO_ELEVATION_VALUES_FOR_ONE_AZIMUTH = <MISSING TRANSLATION!> # unknown parameter {0} UNKNOWN_PARAMETER = ukjent parametere {0} +# unsupported parameter name {0}: supported names {1}, {2} +UNSUPPORTED_PARAMETER_1_2 = <MISSING TRANSLATION!> + +# unknown additional equation +UNKNOWN_ADDITIONAL_EQUATION = <MISSING TRANSLATION!> + +# partial derivatives can be propagated only in cartesian parameters type +PARTIAL_DERIVATIVES_ONLY_IN_CARTESIAN = <MISSING TRANSLATION!> + +# state jacobian is a {0}x{1} matrix, it should be either a 6x6 or a 7x7 matrix +STATE_JACOBIAN_NEITHER_6X6_NOR_7X7 = <MISSING TRANSLATION!> + +# state jacobian has {0} rows but parameters jacobian has {1} rows +STATE_AND_PARAMETERS_JACOBIANS_ROWS_MISMATCH = <MISSING TRANSLATION!> + +# initial jacobian matrix has {0} columns, but {1} parameters have been selected +INITIAL_MATRIX_AND_PARAMETERS_NUMBER_MISMATCH = <MISSING TRANSLATION!> + +# orbit should be either elliptic with a > 0 and e < 1 or hyperbolic with a < 0 and e > 1, a = {0}, e = {1} +ORBIT_A_E_MISMATCH_WITH_CONIC_TYPE = <MISSING TRANSLATION!> + +# hyperbolic orbits cannot be handled as {0} instances +HYPERBOLIC_ORBIT_NOT_HANDLED_AS = <MISSING TRANSLATION!> + # invalid preamble field in CCSDS date: {0} CCSDS_DATE_INVALID_PREAMBLE_FIELD = <MISSING TRANSLATION!> diff --git a/src/site/xdoc/changes.xml b/src/site/xdoc/changes.xml index 2765c28153..fca260b867 100644 --- a/src/site/xdoc/changes.xml +++ b/src/site/xdoc/changes.xml @@ -153,6 +153,33 @@ fixed an error in FramesFactory when getting ITRF2005 and TIRF2000 frames: ignoreTidalEffects was handled wrong. </action> + <action dev="luc" type="update" > + removed serialization of some cached data in frames + </action> + <action dev="luc" type="fix" > + fixed deserialization problems of frame singletons, they were not unique any more + </action> + <action dev="véronique" type="add" > + numerical propagation can now be done either using equinoctial parameters or + cartesian parameters, thus allowing propagation of any kind of trajectories, + including hyperbolic orbits used for interplanetary missions or atmospheric + re-entry trajectories + </action> + <action dev="véronique" type="update" > + completely revamped the partial derivatives matrices computation using the additional + equations mechanism + </action> + <action dev="véronique" type="add" > + added a mechanism to integrate user-supplied additional equations alongside with + orbital parameters during numerical propagation + </action> + <action dev="luc" type="update"> + use A. W. Odell and R. H. Gooding (1986) fast and robust solver for Kepler equation + </action> + <action dev="luc" type="add"> + keplerian and cartesian orbits now support hyperbolic orbits (i.e. eccentricity greater + than 1, and in this case negative semi major axis by convention) + </action> <action dev="luc" type="fix"> fixed an error in LofOffset attitude mode: the computed attitude was reversed with respect to the specification diff --git a/src/test/java/org/orekit/attitudes/LofOffsetPointingTest.java b/src/test/java/org/orekit/attitudes/LofOffsetPointingTest.java index 87bfd4ed05..67c2664e82 100644 --- a/src/test/java/org/orekit/attitudes/LofOffsetPointingTest.java +++ b/src/test/java/org/orekit/attitudes/LofOffsetPointingTest.java @@ -140,7 +140,7 @@ public class LofOffsetPointingTest { sPlus.getAttitude().getRotation(), 2 * h); Assert.assertTrue(spin0.getNorm() > 1.0e-3); - Assert.assertEquals(0.0, spin0.subtract(reference).getNorm(), 4.0e-11); + Assert.assertEquals(0.0, spin0.subtract(reference).getNorm(), 1.0e-10); } diff --git a/src/test/java/org/orekit/attitudes/LofOffsetTest.java b/src/test/java/org/orekit/attitudes/LofOffsetTest.java index 76a5c72634..4e014ac7bd 100644 --- a/src/test/java/org/orekit/attitudes/LofOffsetTest.java +++ b/src/test/java/org/orekit/attitudes/LofOffsetTest.java @@ -195,7 +195,7 @@ public class LofOffsetTest { Vector3D reference = Attitude.estimateSpin(sMinus.getAttitude().getRotation(), sPlus.getAttitude().getRotation(), 2 * h); - Assert.assertEquals(0.0, spin0.subtract(reference).getNorm(), 4.0e-11); + Assert.assertEquals(0.0, spin0.subtract(reference).getNorm(), 1.0e-10); } diff --git a/src/test/java/org/orekit/errors/OrekitMessagesTest.java b/src/test/java/org/orekit/errors/OrekitMessagesTest.java index 799408fd41..97ba642664 100644 --- a/src/test/java/org/orekit/errors/OrekitMessagesTest.java +++ b/src/test/java/org/orekit/errors/OrekitMessagesTest.java @@ -30,7 +30,7 @@ public class OrekitMessagesTest { @Test public void testMessageNumber() { - Assert.assertEquals(89, OrekitMessages.values().length); + Assert.assertEquals(97, OrekitMessages.values().length); } @Test diff --git a/src/test/java/org/orekit/frames/FrameTest.java b/src/test/java/org/orekit/frames/FrameTest.java index 20ee53cf59..74f6a10c84 100644 --- a/src/test/java/org/orekit/frames/FrameTest.java +++ b/src/test/java/org/orekit/frames/FrameTest.java @@ -145,8 +145,8 @@ public class FrameTest { Frame rotatingPadFrame = new TopocentricFrame(new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS, Constants.WGS84_EARTH_FLATTENING, itrf), - new GeodeticPoint(Math.toRadians(5.0), - Math.toRadians(-100.0), + new GeodeticPoint(FastMath.toRadians(5.0), + FastMath.toRadians(-100.0), 0.0), "launch pad"); diff --git a/src/test/java/org/orekit/frames/TODFrameAlternateConfigurationTest.java b/src/test/java/org/orekit/frames/TODFrameAlternateConfigurationTest.java index 0fe8e7451a..193fcab5b3 100644 --- a/src/test/java/org/orekit/frames/TODFrameAlternateConfigurationTest.java +++ b/src/test/java/org/orekit/frames/TODFrameAlternateConfigurationTest.java @@ -142,8 +142,8 @@ public class TODFrameAlternateConfigurationTest { throws OrekitException { super(ignoreNutationCorrection, factoryKey); } - protected void setInterpolatedNutationElements(final double t) { - computeNutationElements(t); + public double[] getInterpolatedNutationElements(final double t) { + return computeNutationElements(t); } } diff --git a/src/test/java/org/orekit/frames/TODFrameTest.java b/src/test/java/org/orekit/frames/TODFrameTest.java index 740a51f095..14391d8739 100644 --- a/src/test/java/org/orekit/frames/TODFrameTest.java +++ b/src/test/java/org/orekit/frames/TODFrameTest.java @@ -46,7 +46,7 @@ public class TODFrameTest { double previousEQE = currentEQE; currentEQE = tod.getEquationOfEquinoxes(d); if (!Double.isNaN(previousEQE)) { - double deltaMicroAS = 3.6e9 * Math.toDegrees(currentEQE - previousEQE); + double deltaMicroAS = 3.6e9 * FastMath.toDegrees(currentEQE - previousEQE); if ((dt - h) * dt > 0) { // away from switch date, equation of equinox should decrease at // about 1.06 micro arcsecond per second diff --git a/src/test/java/org/orekit/orbits/CartesianParametersTest.java b/src/test/java/org/orekit/orbits/CartesianParametersTest.java index eb37fb942b..d7af72d09b 100644 --- a/src/test/java/org/orekit/orbits/CartesianParametersTest.java +++ b/src/test/java/org/orekit/orbits/CartesianParametersTest.java @@ -16,6 +16,12 @@ */ package org.orekit.orbits; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + import org.apache.commons.math.geometry.Vector3D; import org.apache.commons.math.util.FastMath; import org.apache.commons.math.util.MathUtils; @@ -167,6 +173,76 @@ public class CartesianParametersTest { } } + @Test + public void testSerialization() + throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException { + Vector3D position = new Vector3D(-29536113.0, 30329259.0, -100125.0); + Vector3D velocity = new Vector3D(-2194.0, -2141.0, -8.0); + PVCoordinates pvCoordinates = new PVCoordinates( position, velocity); + CartesianOrbit orbit = new CartesianOrbit(pvCoordinates, FramesFactory.getEME2000(), date, mu); + Assert.assertEquals(42255170.003, orbit.getA(), 1.0e-3); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos); + oos.writeObject(orbit); + + Assert.assertTrue(bos.size () > 1400); + Assert.assertTrue(bos.size () < 1500); + + ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); + ObjectInputStream ois = new ObjectInputStream(bis); + CartesianOrbit deserialized = (CartesianOrbit) ois.readObject(); + Vector3D dp = orbit.getPVCoordinates().getPosition().subtract(deserialized.getPVCoordinates().getPosition()); + Vector3D dv = orbit.getPVCoordinates().getVelocity().subtract(deserialized.getPVCoordinates().getVelocity()); + Assert.assertEquals(0.0, dp.getNorm(), 1.0e-10); + Assert.assertEquals(0.0, dv.getNorm(), 1.0e-10); + + } + + @Test + public void testShiftElliptic() { + Vector3D position = new Vector3D(-29536113.0, 30329259.0, -100125.0); + Vector3D velocity = new Vector3D(-2194.0, -2141.0, -8.0); + PVCoordinates pvCoordinates = new PVCoordinates( position, velocity); + CartesianOrbit orbit = new CartesianOrbit(pvCoordinates, FramesFactory.getEME2000(), date, mu); + testShift(orbit, new KeplerianOrbit(orbit), 1.0e-13); + } + + @Test + public void testShiftCircular() { + Vector3D position = new Vector3D(-29536113.0, 30329259.0, -100125.0); + Vector3D velocity = new Vector3D(FastMath.sqrt(mu / position.getNorm()), position.orthogonal()); + PVCoordinates pvCoordinates = new PVCoordinates( position, velocity); + CartesianOrbit orbit = new CartesianOrbit(pvCoordinates, FramesFactory.getEME2000(), date, mu); + testShift(orbit, new CircularOrbit(orbit), 1.0e-15); + } + + @Test + public void testShiftHyperbolic() { + Vector3D position = new Vector3D(-29536113.0, 30329259.0, -100125.0); + Vector3D velocity = new Vector3D(3 * FastMath.sqrt(mu / position.getNorm()), position.orthogonal()); + PVCoordinates pvCoordinates = new PVCoordinates( position, velocity); + CartesianOrbit orbit = new CartesianOrbit(pvCoordinates, FramesFactory.getEME2000(), date, mu); + testShift(orbit, new KeplerianOrbit(orbit), 1.0e-15); + } + + private void testShift(CartesianOrbit tested, Orbit reference, double threshold) { + for (double dt = - 1000; dt < 1000; dt += 10.0) { + + PVCoordinates pvTested = tested.shiftedBy(dt).getPVCoordinates(); + Vector3D pTested = pvTested.getPosition(); + Vector3D vTested = pvTested.getVelocity(); + + PVCoordinates pvReference = reference.shiftedBy(dt).getPVCoordinates(); + Vector3D pReference = pvReference.getPosition(); + Vector3D vReference = pvReference.getVelocity(); + + Assert.assertEquals(0, pTested.subtract(pReference).getNorm(), threshold * pReference.getNorm()); + Assert.assertEquals(0, vTested.subtract(vReference).getNorm(), threshold * vReference.getNorm()); + + } + } + @Test(expected=IllegalArgumentException.class) public void testNonInertialFrame() throws IllegalArgumentException { diff --git a/src/test/java/org/orekit/orbits/KeplerianParametersTest.java b/src/test/java/org/orekit/orbits/KeplerianParametersTest.java index 15d0484293..7bab02c8c3 100644 --- a/src/test/java/org/orekit/orbits/KeplerianParametersTest.java +++ b/src/test/java/org/orekit/orbits/KeplerianParametersTest.java @@ -79,6 +79,24 @@ public class KeplerianParametersTest { Assert.assertEquals(MathUtils.normalizeAngle(paramCir.getLE(), kepCir.getLE()), kepCir.getLE(), Utils.epsilonAngle * FastMath.abs(kepCir.getLE())); Assert.assertEquals(MathUtils.normalizeAngle(paramCir.getLv(), kepCir.getLv()), kepCir.getLv(), Utils.epsilonAngle * FastMath.abs(kepCir.getLv())); + // hyperbolic orbit + KeplerianOrbit kepHyp = + new KeplerianOrbit(-24464560.0, 1.7311, 0.122138, 3.10686, 1.00681, + 0.048363, KeplerianOrbit.MEAN_ANOMALY, + FramesFactory.getEME2000(), date, mu); + + Vector3D posHyp = kepHyp.getPVCoordinates().getPosition(); + Vector3D vitHyp = kepHyp.getPVCoordinates().getVelocity(); + + KeplerianOrbit paramHyp = new KeplerianOrbit(new PVCoordinates(posHyp,vitHyp), + FramesFactory.getEME2000(), date, mu); + Assert.assertEquals(paramHyp.getA(), kepHyp.getA(), Utils.epsilonTest * FastMath.abs(kepHyp.getA())); + Assert.assertEquals(paramHyp.getE(), kepHyp.getE(), Utils.epsilonE * FastMath.abs(kepHyp.getE())); + Assert.assertEquals(MathUtils.normalizeAngle(paramHyp.getI(), kepHyp.getI()), kepHyp.getI(), Utils.epsilonAngle * FastMath.abs(kepHyp.getI())); + Assert.assertEquals(MathUtils.normalizeAngle(paramHyp.getPerigeeArgument(), kepHyp.getPerigeeArgument()), kepHyp.getPerigeeArgument(), Utils.epsilonAngle * FastMath.abs(kepHyp.getPerigeeArgument())); + Assert.assertEquals(MathUtils.normalizeAngle(paramHyp.getRightAscensionOfAscendingNode(), kepHyp.getRightAscensionOfAscendingNode()), kepHyp.getRightAscensionOfAscendingNode(), Utils.epsilonAngle * FastMath.abs(kepHyp.getRightAscensionOfAscendingNode())); + Assert.assertEquals(MathUtils.normalizeAngle(paramHyp.getMeanAnomaly(), kepHyp.getMeanAnomaly()), kepHyp.getMeanAnomaly(), Utils.epsilonAngle * FastMath.abs(kepHyp.getMeanAnomaly())); + } public void testKeplerianToCartesian() { @@ -377,7 +395,7 @@ public class KeplerianParametersTest { @Test public void testSymmetry() { - // elliptic and non equatorail orbit + // elliptic and non equatorial orbit Vector3D position = new Vector3D(-4947831., -3765382., -3708221.); Vector3D velocity = new Vector3D(-2079., 5291., -7842.); double mu = 3.9860047e14; @@ -426,6 +444,53 @@ public class KeplerianParametersTest { Assert.assertEquals(0.00094277682051291315229, orbit.getKeplerianMeanMotion(), 1.0e-16); } + @Test + public void testHyperbola() { + KeplerianOrbit orbit = new KeplerianOrbit(-10000000.0, 2.5, 0.3, 0, 0, 0.0, + KeplerianOrbit.TRUE_ANOMALY, + FramesFactory.getEME2000(), AbsoluteDate.J2000_EPOCH, + mu); + Vector3D perigeeP = orbit.getPVCoordinates().getPosition(); + Vector3D u = perigeeP.normalize(); + Vector3D focus1 = Vector3D.ZERO; + Vector3D focus2 = new Vector3D(-2 * orbit.getA() * orbit.getE(), u); + for (double dt = -5000; dt < 5000; dt += 60) { + PVCoordinates pv = orbit.shiftedBy(dt).getPVCoordinates(); + double d1 = Vector3D.distance(pv.getPosition(), focus1); + double d2 = Vector3D.distance(pv.getPosition(), focus2); + Assert.assertEquals(-2 * orbit.getA(), FastMath.abs(d1 - d2), 1.0e-6); + KeplerianOrbit rebuilt = + new KeplerianOrbit(pv, orbit.getFrame(), orbit.getDate().shiftedBy(dt), mu); + Assert.assertEquals(-10000000.0, rebuilt.getA(), 1.0e-6); + Assert.assertEquals(2.5, rebuilt.getE(), 1.0e-13); + } + } + + @Test + public void testKeplerEquation() { + + for (double M = -6 * FastMath.PI; M < 6 * FastMath.PI; M += 0.01) { + KeplerianOrbit pElliptic = + new KeplerianOrbit(24464560.0, 0.7311, 2.1, 3.10686, 1.00681, + M, KeplerianOrbit.MEAN_ANOMALY, + FramesFactory.getEME2000(), date, mu); + double E = pElliptic.getEccentricAnomaly(); + double e = pElliptic.getE(); + Assert.assertEquals(M, E - e * FastMath.sin(E), 2.0e-14); + } + + for (double M = -6 * FastMath.PI; M < 6 * FastMath.PI; M += 0.01) { + KeplerianOrbit pAlmostParabolic = + new KeplerianOrbit(24464560.0, 0.9999, 2.1, 3.10686, 1.00681, + M, KeplerianOrbit.MEAN_ANOMALY, + FramesFactory.getEME2000(), date, mu); + double E = pAlmostParabolic.getEccentricAnomaly(); + double e = pAlmostParabolic.getE(); + Assert.assertEquals(M, E - e * FastMath.sin(E), 3.0e-13); + } + + } + @Before public void setUp() { diff --git a/src/test/java/org/orekit/propagation/SpacecraftStateTest.java b/src/test/java/org/orekit/propagation/SpacecraftStateTest.java index 7f7804cf4e..ccaa10df78 100644 --- a/src/test/java/org/orekit/propagation/SpacecraftStateTest.java +++ b/src/test/java/org/orekit/propagation/SpacecraftStateTest.java @@ -24,7 +24,6 @@ import junit.framework.Assert; import org.apache.commons.math.analysis.polynomials.PolynomialFunction; import org.apache.commons.math.geometry.Rotation; import org.apache.commons.math.geometry.Vector3D; -import org.apache.commons.math.optimization.OptimizationException; import org.apache.commons.math.util.FastMath; import org.junit.After; import org.junit.Before; @@ -50,7 +49,7 @@ public class SpacecraftStateTest { @Test public void testInterpolationError() - throws ParseException, OrekitException, OptimizationException { + throws ParseException, OrekitException { // polynomial models for interpolation error in position, velocity and attitude @@ -110,7 +109,7 @@ public class SpacecraftStateTest { @Test public void testTransform() - throws ParseException, OrekitException, OptimizationException { + throws ParseException, OrekitException { double maxDP = 0; double maxDV = 0; diff --git a/src/test/java/org/orekit/propagation/analytical/EcksteinHechlerPropagatorTest.java b/src/test/java/org/orekit/propagation/analytical/EcksteinHechlerPropagatorTest.java index 71e180177c..05a3c4f181 100644 --- a/src/test/java/org/orekit/propagation/analytical/EcksteinHechlerPropagatorTest.java +++ b/src/test/java/org/orekit/propagation/analytical/EcksteinHechlerPropagatorTest.java @@ -488,7 +488,7 @@ public class EcksteinHechlerPropagatorTest { @Test(expected = PropagationException.class) public void hyperbolic() throws PropagationException { KeplerianOrbit hyperbolic = - new KeplerianOrbit(1.0e10, 2, 0, 0, 0, 0, KeplerianOrbit.TRUE_ANOMALY, + new KeplerianOrbit(-1.0e10, 2, 0, 0, 0, 0, KeplerianOrbit.TRUE_ANOMALY, FramesFactory.getEME2000(), AbsoluteDate.J2000_EPOCH, 3.986004415e14); EcksteinHechlerPropagator propagator = new EcksteinHechlerPropagator(hyperbolic, ae, mu, c20, c30, c40, c50, c60); diff --git a/src/test/java/org/orekit/propagation/events/DetectorTest.java b/src/test/java/org/orekit/propagation/events/DetectorTest.java index 6aa91050a1..edeba3e102 100644 --- a/src/test/java/org/orekit/propagation/events/DetectorTest.java +++ b/src/test/java/org/orekit/propagation/events/DetectorTest.java @@ -33,6 +33,7 @@ import org.orekit.propagation.sampling.OrekitFixedStepHandler; import org.orekit.time.AbsoluteDate; import org.orekit.time.TimeScale; import org.orekit.time.TimeScalesFactory; +import org.orekit.utils.Constants; import org.orekit.utils.PVCoordinates; public class DetectorTest { @@ -98,6 +99,7 @@ public class DetectorTest { @Before public void setUp() { Utils.setDataRoot("regular-data"); + mu = Constants.EIGEN5C_EARTH_MU; } } diff --git a/src/test/java/org/orekit/propagation/numerical/NumericalPropagatorTest.java b/src/test/java/org/orekit/propagation/numerical/NumericalPropagatorTest.java index 6d3f540417..3b9bf564a1 100644 --- a/src/test/java/org/orekit/propagation/numerical/NumericalPropagatorTest.java +++ b/src/test/java/org/orekit/propagation/numerical/NumericalPropagatorTest.java @@ -16,7 +16,10 @@ */ package org.orekit.propagation.numerical; -import org.apache.commons.math.exception.util.DummyLocalizable; +import java.io.IOException; +import java.text.ParseException; + +import org.apache.commons.math.exception.util.LocalizedFormats; import org.apache.commons.math.geometry.Vector3D; import org.apache.commons.math.ode.nonstiff.AdaptiveStepsizeIntegrator; import org.apache.commons.math.ode.nonstiff.ClassicalRungeKuttaIntegrator; @@ -26,16 +29,23 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.orekit.Utils; import org.orekit.errors.OrekitException; import org.orekit.errors.PropagationException; +import org.orekit.frames.Frame; import org.orekit.frames.FramesFactory; +import org.orekit.orbits.CartesianOrbit; import org.orekit.orbits.EquinoctialOrbit; import org.orekit.orbits.Orbit; import org.orekit.propagation.SpacecraftState; +import org.orekit.propagation.events.ApsideDetector; import org.orekit.propagation.events.DateDetector; +import org.orekit.propagation.numerical.NumericalPropagator.PropagationParametersType; import org.orekit.propagation.sampling.OrekitStepHandler; import org.orekit.propagation.sampling.OrekitStepInterpolator; import org.orekit.time.AbsoluteDate; +import org.orekit.time.TimeScale; +import org.orekit.time.TimeScalesFactory; import org.orekit.utils.PVCoordinates; @@ -97,6 +107,26 @@ public class NumericalPropagatorTest { } + @Test + public void testCartesian() throws OrekitException { + + // Propagation of the initial at t + dt + final double dt = 3200; + propagator.setPropagationParametersType(NumericalPropagator.PropagationParametersType.CARTESIAN); + final PVCoordinates finalState = + propagator.propagate(initDate.shiftedBy(dt)).getPVCoordinates(); + final Vector3D pFin = finalState.getPosition(); + final Vector3D vFin = finalState.getVelocity(); + + // Check results + final PVCoordinates reference = initialState.shiftedBy(dt).getPVCoordinates(); + final Vector3D pRef = reference.getPosition(); + final Vector3D vRef = reference.getVelocity(); + Assert.assertEquals(0, pRef.subtract(pFin).getNorm(), 0.4); + Assert.assertEquals(0, vRef.subtract(vFin).getNorm(), 0.0002); + + } + @Test(expected=OrekitException.class) public void testException() throws OrekitException { propagator.setMasterMode(new OrekitStepHandler() { @@ -109,7 +139,7 @@ public class NumericalPropagatorTest { Assert.assertTrue(interpolator.getInterpolatedDate().compareTo(previousCall) < 0); } if (--countDown == 0) { - throw new PropagationException((Throwable) null, new DummyLocalizable("dummy error")); + throw new PropagationException(LocalizedFormats.SIMPLE_MESSAGE, "dummy error"); } } public boolean requiresDenseOutput() { @@ -211,8 +241,70 @@ public class NumericalPropagatorTest { this.gotHere = gotHere; } + @Test + public void testEventDetectionBug() throws OrekitException, IOException, ParseException { + + TimeScale utc = TimeScalesFactory.getUTC(); + AbsoluteDate initialDate = new AbsoluteDate(2005, 1, 1, 0, 0, 0.0, utc); + double duration = 100000.; + AbsoluteDate endDate = new AbsoluteDate(initialDate,duration); + + // Initialization of the frame EME2000 + Frame EME2000 = FramesFactory.getEME2000(); + + + // Initial orbit + double a = 35786000. + 6378137.0; + double e = 0.70; + double rApogee = a*(1+e); + double vApogee = FastMath.sqrt(mu*(1-e)/(a*(1+e))); + Orbit geo = new CartesianOrbit(new PVCoordinates(new Vector3D(rApogee, 0., 0.), + new Vector3D(0., vApogee, 0.)), EME2000, + initialDate, mu); + + + duration = geo.getKeplerianPeriod(); + endDate = new AbsoluteDate(initialDate, duration); + + // Numerical Integration + final double minStep = 0.001; + final double maxStep = 1000; + final double initStep = 60; + final double[] absTolerance = { + 0.001, 1.0e-9, 1.0e-9, 1.0e-6, 1.0e-6, 1.0e-6, 0.001}; + final double[] relTolerance = { + 1.0e-7, 1.0e-4, 1.0e-4, 1.0e-7, 1.0e-7, 1.0e-7, 1.0e-7}; + + AdaptiveStepsizeIntegrator integrator = + new DormandPrince853Integrator(minStep, maxStep, absTolerance, relTolerance); + integrator.setInitialStepSize(initStep); + + // Numerical propagator based on the integrator + propagator = new NumericalPropagator(integrator); + double mass = 1000.; + SpacecraftState initialState = new SpacecraftState(geo, mass); + propagator.setInitialState(initialState); + propagator.setPropagationParametersType(PropagationParametersType.CARTESIAN); + + + // Set the events Detectors + ApsideDetector event1 = new ApsideDetector(geo); + propagator.addEventDetector(event1); + + // Set the propagation mode + propagator.setSlaveMode(); + + // Propagate + SpacecraftState finalState = propagator.propagate(endDate); + + // we should stop long before endDate + Assert.assertTrue(endDate.durationFrom(finalState.getDate()) > 40000.0); + } + + @Before public void setUp() { + Utils.setDataRoot("compressed-data"); mu = 3.9860047e14; final Vector3D position = new Vector3D(7.0e6, 1.0e6, 4.0e6); final Vector3D velocity = new Vector3D(-500.0, 8000.0, 1000.0); diff --git a/src/test/java/org/orekit/propagation/numerical/NumericalPropagatorWithJacobiansTest.java b/src/test/java/org/orekit/propagation/numerical/NumericalPropagatorWithJacobiansTest.java deleted file mode 100644 index 8b1cf93c34..0000000000 --- a/src/test/java/org/orekit/propagation/numerical/NumericalPropagatorWithJacobiansTest.java +++ /dev/null @@ -1,347 +0,0 @@ -/* Copyright 2002-2010 CS Communication & Systèmes - * Licensed to CS Communication & Systèmes (CS) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * CS licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.orekit.propagation.numerical; - -import org.apache.commons.math.ode.nonstiff.AdaptiveStepsizeIntegrator; -import org.apache.commons.math.ode.nonstiff.DormandPrince853Integrator; -import org.apache.commons.math.util.FastMath; -import org.apache.commons.math.util.MathUtils; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.orekit.Utils; -import org.orekit.bodies.CelestialBodyFactory; -import org.orekit.errors.OrekitException; -import org.orekit.errors.PropagationException; -import org.orekit.forces.ForceModelWithJacobians; -import org.orekit.forces.SphericalSpacecraft; -import org.orekit.forces.drag.DragForce; -import org.orekit.forces.radiation.SolarRadiationPressure; -import org.orekit.frames.FramesFactory; -import org.orekit.orbits.EquinoctialOrbit; -import org.orekit.propagation.SpacecraftState; -import org.orekit.time.AbsoluteDate; -import org.orekit.time.TimeScalesFactory; -import org.orekit.utils.Constants; - - -public class NumericalPropagatorWithJacobiansTest { - - private AbsoluteDate initDate; - private SphericalSpacecraft spaceCraft; - private SpacecraftState initialState; - private NumericalPropagatorWithJacobians propagator; - - // Integrator parameters - private final double minStep = 0.001; - private final double maxStep = 1000.0; - private final double[] absTolV = {0.001, 1.0e-9, 1.0e-9, 1.0e-6, 1.0e-6, 1.0e-6, 0.001}; - private final double[] relTolV = {1.0e-10, 1.0e-7, 1.0e-7, 1.0e-10, 1.0e-10, 1.0e-10, 1.0e-10}; - - // Orbit parameters : position = (7.0e6, 1.0e6, 4.0e6) ; velocity = (-500.0, 8000.0, 1000.0) - private final double a = 1.2123382253763368E7; - private final double ex = 0.33088598908457206; - private final double ey = -0.119544860585116; - private final double hx = 0.07403074327464344; - private final double hy = -0.25499478239043855; - private final double lv = 0.1602263984451438; - private final double mu = 3.9860047e14; - - // Force models parameters - private final double drag = 0.4; // drag coeff. - private final double srpr = 0.9; // solar radiation pressure coeff. - - // Threshold for test acceptance - private final double ERRMAX = FastMath.sqrt(MathUtils.EPSILON); - - @Test - public void testWithoutJacobian() throws OrekitException { - - // Propagation of the initial at t + dt - // NumericalPropagatorWithJacobians behaves just the - // same as NumericalPropagator for simple propagation - final double dt = 300; - final SpacecraftState finalState = - propagator.propagate(initDate.shiftedBy(dt)); - - // Check results - final double n = FastMath.sqrt(initialState.getMu() / initialState.getA()) / initialState.getA(); - Assert.assertEquals(initialState.getA(), finalState.getA(), 1.0e-10); - Assert.assertEquals(initialState.getEquinoctialEx(), finalState.getEquinoctialEx(), 1.0e-10); - Assert.assertEquals(initialState.getEquinoctialEy(), finalState.getEquinoctialEy(), 1.0e-10); - Assert.assertEquals(initialState.getHx(), finalState.getHx(), 1.0e-10); - Assert.assertEquals(initialState.getHy(), finalState.getHy(), 1.0e-10); - Assert.assertEquals(initialState.getLM() + n * dt, finalState.getLM(), 2.0e-8); - } - - @Test - public void testKeplerianJacobian() throws OrekitException { - double[][] dFdY = new double[7][7]; - double[][] dFdP = new double[7][0]; - final double dt = 300; - propagator.setInitialState(initialState); - propagator.propagate(initDate.shiftedBy(dt), dFdY, dFdP); - - // Check results - // without forces, the orbit is keplerian - // all elements but the latitude argument are constant - for (int i = 0; i < dFdY.length; i++) { - if (i != 5) { - for (int j = 0; j < dFdY[i].length; j++) { - if (i != j) { - Assert.assertEquals(dFdY[i][j], 0.0, MathUtils.EPSILON); - } else { - Assert.assertEquals(dFdY[i][j], 1.0, MathUtils.EPSILON); - } - } - } - } - } - - @Test - public void testOrbitalParametersJacobian() throws OrekitException { - - double[][] dFdY = new double[7][7]; - double[][] dFdP = new double[7][0]; - final double dt = 3; - - ForceModelWithJacobians sunPressure = - new SolarRadiationPressure(CelestialBodyFactory.getSun(), - Constants.WGS84_EARTH_EQUATORIAL_RADIUS, spaceCraft); - propagator.addForceModel(sunPressure); - propagator.setInitialState(initialState); - final SpacecraftState finalState = - propagator.propagate(initDate.shiftedBy(dt), dFdY, dFdP); - - final double da = absTolV[0]*1.e1; - propagator.resetInitialState( - new SpacecraftState(new EquinoctialOrbit(a + da, ex, ey, hx, hy, lv, - EquinoctialOrbit.TRUE_LATITUDE_ARGUMENT, - FramesFactory.getEME2000(), initDate, mu))); - final SpacecraftState endStateDa = propagator.propagate(initDate.shiftedBy(dt)); - // Check results - final double dAda0 = (endStateDa.getA() - finalState.getA()) / da; - final double dExda0 = (endStateDa.getEquinoctialEx() - finalState.getEquinoctialEx()) / da; - final double dEyda0 = (endStateDa.getEquinoctialEy() - finalState.getEquinoctialEy()) / da; - final double dHxda0 = (endStateDa.getHx() - finalState.getHx()) / da; - final double dHyda0 = (endStateDa.getHy() - finalState.getHy()) / da; - final double dLvda0 = (endStateDa.getLv() - finalState.getLv()) / da; - final double dMda0 = (endStateDa.getMass() - finalState.getMass()) / da; - - Assert.assertTrue(FastMath.abs((dAda0 - dFdY[0][0])/finalState.getA()) < ERRMAX); - Assert.assertTrue(FastMath.abs((dExda0 - dFdY[1][0])/finalState.getEquinoctialEx()) < ERRMAX); - Assert.assertTrue(FastMath.abs((dEyda0 - dFdY[2][0])/finalState.getEquinoctialEy()) < ERRMAX); - Assert.assertTrue(FastMath.abs((dHxda0 - dFdY[3][0])/finalState.getHx()) < ERRMAX); - Assert.assertTrue(FastMath.abs((dHyda0 - dFdY[4][0])/finalState.getHy()) < ERRMAX); - Assert.assertTrue(FastMath.abs((dLvda0 - dFdY[5][0])/finalState.getHy()) < ERRMAX); - Assert.assertEquals(dMda0, dFdY[6][0], 0.0); - - final double dex = absTolV[1]*1.e1; - propagator.resetInitialState( - new SpacecraftState(new EquinoctialOrbit(a, ex + dex, ey, hx, hy, lv, - EquinoctialOrbit.TRUE_LATITUDE_ARGUMENT, - FramesFactory.getEME2000(), initDate, mu))); - final SpacecraftState endStateDex = propagator.propagate(initDate.shiftedBy(dt)); - // Check results - final double dAdex0 = (endStateDex.getA() - finalState.getA()) / dex; - final double dExdex0 = (endStateDex.getEquinoctialEx() - finalState.getEquinoctialEx()) / dex; - final double dEydex0 = (endStateDex.getEquinoctialEy() - finalState.getEquinoctialEy()) / dex; - final double dHxdex0 = (endStateDex.getHx() - finalState.getHx()) / dex; - final double dHydex0 = (endStateDex.getHy() - finalState.getHy()) / dex; - final double dLvdex0 = (endStateDex.getLv() - finalState.getLv()) / dex; - final double dMdex0 = (endStateDex.getMass() - finalState.getMass()) / dex; - - Assert.assertTrue(FastMath.abs((dAdex0 - dFdY[0][1])/finalState.getA()) < ERRMAX); - Assert.assertTrue(FastMath.abs((dExdex0 - dFdY[1][1])/finalState.getEquinoctialEx()) < ERRMAX); - Assert.assertTrue(FastMath.abs((dEydex0 - dFdY[2][1])/finalState.getEquinoctialEy()) < ERRMAX); - Assert.assertTrue(FastMath.abs((dHxdex0 - dFdY[3][1])/finalState.getHx()) < ERRMAX); - Assert.assertTrue(FastMath.abs((dHydex0 - dFdY[4][1])/finalState.getHy()) < ERRMAX); - Assert.assertTrue(FastMath.abs((dLvdex0 - dFdY[5][1])/finalState.getHy()) < ERRMAX); - Assert.assertEquals(dMdex0, dFdY[6][1], 0.0); - - final double dey = absTolV[2]*1.e1; - propagator.resetInitialState( - new SpacecraftState(new EquinoctialOrbit(a, ex, ey + dey, hx, hy, lv, - EquinoctialOrbit.TRUE_LATITUDE_ARGUMENT, - FramesFactory.getEME2000(), initDate, mu))); - final SpacecraftState endStateDey = propagator.propagate(initDate.shiftedBy(dt)); - // Check results - final double dAdey0 = (endStateDey.getA() - finalState.getA()) / dey; - final double dExdey0 = (endStateDey.getEquinoctialEx() - finalState.getEquinoctialEx()) / dey; - final double dEydey0 = (endStateDey.getEquinoctialEy() - finalState.getEquinoctialEy()) / dey; - final double dHxdey0 = (endStateDey.getHx() - finalState.getHx()) / dey; - final double dHydey0 = (endStateDey.getHy() - finalState.getHy()) / dey; - final double dLvdey0 = (endStateDey.getLv() - finalState.getLv()) / dey; - final double dMdey0 = (endStateDey.getMass() - finalState.getMass()) / dey; - - Assert.assertTrue(FastMath.abs(dAdey0 - dFdY[0][2])/finalState.getA() < ERRMAX); - Assert.assertTrue(FastMath.abs(dExdey0 - dFdY[1][2])/finalState.getEquinoctialEx() < ERRMAX); - Assert.assertTrue(FastMath.abs(dEydey0 - dFdY[2][2])/finalState.getEquinoctialEy() < ERRMAX); - Assert.assertTrue(FastMath.abs(dHxdey0 - dFdY[3][2])/finalState.getHx() < ERRMAX); - Assert.assertTrue(FastMath.abs(dHydey0 - dFdY[4][2])/finalState.getHy() < ERRMAX); - Assert.assertTrue(FastMath.abs(dLvdey0 - dFdY[5][2])/finalState.getLv() < ERRMAX); - Assert.assertEquals(dMdey0, dFdY[6][2], 0.0); - - final double dhx = absTolV[3]*1.e1; - propagator.resetInitialState( - new SpacecraftState(new EquinoctialOrbit(a, ex, ey, hx + dhx, hy, lv, - EquinoctialOrbit.TRUE_LATITUDE_ARGUMENT, - FramesFactory.getEME2000(), initDate, mu))); - final SpacecraftState endStateDhx = propagator.propagate(initDate.shiftedBy(dt)); - // Check results - final double dAdhx0 = (endStateDhx.getA() - finalState.getA()) / dhx; - final double dExdhx0 = (endStateDhx.getEquinoctialEx() - finalState.getEquinoctialEx()) / dhx; - final double dEydhx0 = (endStateDhx.getEquinoctialEy() - finalState.getEquinoctialEy()) / dhx; - final double dHxdhx0 = (endStateDhx.getHx() - finalState.getHx()) / dhx; - final double dHydhx0 = (endStateDhx.getHy() - finalState.getHy()) / dhx; - final double dLvdhx0 = (endStateDhx.getLv() - finalState.getLv()) / dhx; - final double dMdhx0 = (endStateDhx.getMass() - finalState.getMass()) / dhx; - - Assert.assertTrue(FastMath.abs(dAdhx0 - dFdY[0][3])/finalState.getA() < ERRMAX); - Assert.assertTrue(FastMath.abs(dExdhx0 - dFdY[1][3])/finalState.getEquinoctialEx() < ERRMAX); - Assert.assertTrue(FastMath.abs(dEydhx0 - dFdY[2][3])/finalState.getEquinoctialEy() < ERRMAX); - Assert.assertTrue(FastMath.abs(dHxdhx0 - dFdY[3][3])/finalState.getHx() < ERRMAX); - Assert.assertTrue(FastMath.abs(dHydhx0 - dFdY[4][3])/finalState.getHy() < ERRMAX); - Assert.assertTrue(FastMath.abs(dLvdhx0 - dFdY[5][3])/finalState.getLv() < ERRMAX); - Assert.assertEquals(dMdhx0, dFdY[6][3], 0.0); - - final double dhy = absTolV[4]*1.e1; - propagator.resetInitialState( - new SpacecraftState(new EquinoctialOrbit(a, ex, ey, hx, hy + dhy, lv, - EquinoctialOrbit.TRUE_LATITUDE_ARGUMENT, - FramesFactory.getEME2000(), initDate, mu))); - final SpacecraftState endStateDhy = propagator.propagate(initDate.shiftedBy(dt)); - // Check results - final double dAdhy0 = (endStateDhy.getA() - finalState.getA()) / dhy; - final double dExdhy0 = (endStateDhy.getEquinoctialEx() - finalState.getEquinoctialEx()) / dhy; - final double dEydhy0 = (endStateDhy.getEquinoctialEy() - finalState.getEquinoctialEy()) / dhy; - final double dHxdhy0 = (endStateDhy.getHx() - finalState.getHx()) / dhy; - final double dHydhy0 = (endStateDhy.getHy() - finalState.getHy()) / dhy; - final double dLvdhy0 = (endStateDhy.getLv() - finalState.getLv()) / dhy; - final double dMdhy0 = (endStateDhy.getMass() - finalState.getMass()) / dhy; - - Assert.assertTrue(FastMath.abs(dAdhy0 - dFdY[0][4])/finalState.getA() < ERRMAX); - Assert.assertTrue(FastMath.abs(dExdhy0 - dFdY[1][4])/finalState.getEquinoctialEx() < ERRMAX); - Assert.assertTrue(FastMath.abs(dEydhy0 - dFdY[2][4])/finalState.getEquinoctialEy() < ERRMAX); - Assert.assertTrue(FastMath.abs(dHxdhy0 - dFdY[3][4])/finalState.getHx() < ERRMAX); - Assert.assertTrue(FastMath.abs(dHydhy0 - dFdY[4][4])/finalState.getHy() < ERRMAX); - Assert.assertTrue(FastMath.abs(dLvdhy0 - dFdY[5][4])/finalState.getLv() < ERRMAX); - Assert.assertEquals(dMdhy0, dFdY[6][4], 0.0); - - final double dlv = absTolV[5]*1.e1; - propagator.resetInitialState( - new SpacecraftState(new EquinoctialOrbit(a, ex, ey, hx, hy, lv + dlv, - EquinoctialOrbit.TRUE_LATITUDE_ARGUMENT, - FramesFactory.getEME2000(), initDate, mu))); - final SpacecraftState endStateDlv = propagator.propagate(initDate.shiftedBy(dt)); - // Check results - final double dAdlv0 = (endStateDlv.getA() - finalState.getA()) / dlv; - final double dExdlv0 = (endStateDlv.getEquinoctialEx() - finalState.getEquinoctialEx()) / dlv; - final double dEydlv0 = (endStateDlv.getEquinoctialEy() - finalState.getEquinoctialEy()) / dlv; - final double dHxdlv0 = (endStateDlv.getHx() - finalState.getHx()) / dlv; - final double dHydlv0 = (endStateDlv.getHy() - finalState.getHy()) / dlv; - final double dLvdlv0 = (endStateDlv.getLv() - finalState.getLv()) / dlv; - final double dMdlv0 = (endStateDlv.getMass() - finalState.getMass()) / dlv; - - Assert.assertTrue(FastMath.abs(dAdlv0 - dFdY[0][5])/finalState.getA() < ERRMAX); - Assert.assertTrue(FastMath.abs(dExdlv0 - dFdY[1][5])/finalState.getEquinoctialEx() < ERRMAX); - Assert.assertTrue(FastMath.abs(dEydlv0 - dFdY[2][5])/finalState.getEquinoctialEy() < ERRMAX); - Assert.assertTrue(FastMath.abs(dHxdlv0 - dFdY[3][5])/finalState.getHx() < ERRMAX); - Assert.assertTrue(FastMath.abs(dHydlv0 - dFdY[4][5])/finalState.getHy() < ERRMAX); - Assert.assertTrue(FastMath.abs(dLvdlv0 - dFdY[5][5])/finalState.getLv() < ERRMAX); - Assert.assertEquals(dMdlv0, dFdY[6][5], 0.0); - } - - @Test - public void testForceParameterJacobian() throws OrekitException { - double[][] dFdY = new double[7][7]; - double[][] dFdP = new double[7][1]; - final double dt = 300; - - ForceModelWithJacobians sunPressure = - new SolarRadiationPressure(CelestialBodyFactory.getSun(), - Constants.WGS84_EARTH_EQUATORIAL_RADIUS, spaceCraft); - propagator.addForceModel(sunPressure); - propagator.selectParameters(new String[] {SolarRadiationPressure.ABSORPTION_COEFFICIENT}); - propagator.setInitialState(initialState); - final SpacecraftState finalState = propagator.propagate(initDate.shiftedBy(dt), dFdY, dFdP); - - propagator.removeForceModels(); - final double dp = srpr * 0.1; - final double srprDP = srpr + dp; - sunPressure.setParameter(SolarRadiationPressure.ABSORPTION_COEFFICIENT, srprDP); - propagator.addForceModel(sunPressure); - propagator.setInitialState(initialState); - final SpacecraftState endStateDP = propagator.propagate(initDate.shiftedBy(dt)); - - // Check results - final double dAdP = (endStateDP.getA() - finalState.getA()) / dp; - final double dExdP = (endStateDP.getEquinoctialEx() - finalState.getEquinoctialEx()) / dp; - final double dEydP = (endStateDP.getEquinoctialEy() - finalState.getEquinoctialEy()) / dp; - final double dHxdP = (endStateDP.getHx() - finalState.getHx()) / dp; - final double dHydP = (endStateDP.getHy() - finalState.getHy()) / dp; - final double dLvdP = (endStateDP.getLv() - finalState.getLv()) / dp; - - Assert.assertTrue(FastMath.abs(dAdP - dFdP[0][0])/finalState.getA() < ERRMAX); - Assert.assertTrue(FastMath.abs(dExdP - dFdP[1][0])/finalState.getEquinoctialEx() < ERRMAX); - Assert.assertTrue(FastMath.abs(dEydP - dFdP[2][0])/finalState.getEquinoctialEy() < ERRMAX); - Assert.assertTrue(FastMath.abs(dHxdP - dFdP[3][0])/finalState.getHx() < ERRMAX); - Assert.assertTrue(FastMath.abs(dHydP - dFdP[4][0])/finalState.getHy() < ERRMAX); - Assert.assertTrue(FastMath.abs(dLvdP - dFdP[5][0])/finalState.getLv() < ERRMAX); - } - - @Test(expected=PropagationException.class) - public void testException() throws OrekitException { - double[][] dFdY = null; - double[][] dFdP = null; - final double dt = 300; - propagator.removeForceModels(); - propagator.selectParameters(new String[] {DragForce.DRAG_COEFFICIENT}); - propagator.propagate(initDate.shiftedBy(dt), dFdY, dFdP); - } - - @Before - public void setUp() { - - Utils.setDataRoot("regular-data"); - - initDate = new AbsoluteDate(2003, 1, 1, TimeScalesFactory.getTAI()); - initialState = - new SpacecraftState( - new EquinoctialOrbit(a, ex, ey, hx, hy, lv, - EquinoctialOrbit.TRUE_LATITUDE_ARGUMENT, - FramesFactory.getEME2000(), initDate, mu)); - AdaptiveStepsizeIntegrator integrator = - new DormandPrince853Integrator(minStep, maxStep, absTolV, relTolV); - propagator = new NumericalPropagatorWithJacobians(integrator); - propagator.setInitialState(initialState); - - // Spacecraft definition for force models - final double surf = 25.; - spaceCraft = new SphericalSpacecraft(surf, drag, srpr, srpr); - } - - @After - public void tearDown() { - initDate = null; - initialState = null; - propagator = null; - } - -} - diff --git a/src/test/java/org/orekit/propagation/numerical/IntegratedEphemerisTest.java b/src/test/java/org/orekit/propagation/precomputed/IntegratedEphemerisTest.java similarity index 97% rename from src/test/java/org/orekit/propagation/numerical/IntegratedEphemerisTest.java rename to src/test/java/org/orekit/propagation/precomputed/IntegratedEphemerisTest.java index 59e8058f85..bf832d23ea 100644 --- a/src/test/java/org/orekit/propagation/numerical/IntegratedEphemerisTest.java +++ b/src/test/java/org/orekit/propagation/precomputed/IntegratedEphemerisTest.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.orekit.propagation.numerical; +package org.orekit.propagation.precomputed; import java.io.FileNotFoundException; @@ -30,6 +30,7 @@ import org.orekit.orbits.Orbit; import org.orekit.propagation.BoundedPropagator; import org.orekit.propagation.SpacecraftState; import org.orekit.propagation.analytical.KeplerianPropagator; +import org.orekit.propagation.numerical.NumericalPropagator; import org.orekit.time.AbsoluteDate; import org.orekit.utils.Constants; import org.orekit.utils.PVCoordinates; diff --git a/src/test/java/org/orekit/time/AbsoluteDateTest.java b/src/test/java/org/orekit/time/AbsoluteDateTest.java index 71c93529cc..2959237671 100644 --- a/src/test/java/org/orekit/time/AbsoluteDateTest.java +++ b/src/test/java/org/orekit/time/AbsoluteDateTest.java @@ -248,7 +248,7 @@ public class AbsoluteDateTest { public void testCCSDSUnsegmented() throws OrekitException { AbsoluteDate reference = new AbsoluteDate("2002-05-23T12:34:56.789", TimeScalesFactory.getUTC()); - double lsb = Math.pow(2.0, -24); + double lsb = FastMath.pow(2.0, -24); byte[] timeCCSDSEpoch = new byte[] { 0x53, 0x7F, 0x40, -0x70, -0x37, -0x05, -0x19 }; for (int preamble = 0x00; preamble < 0x100; ++preamble) { -- GitLab