Degenerate Conic

Algorithms • Modern Fortran Programming • Orbital Mechanics

May 25, 2026

Converting Legacy C++ Code To Modern Fortran

"I am bound Upon a wheel of fire, that mine own tears Do scald like molten lead." - Shakespeare, "King Lear", Act IV, Scene 7

converting_cpp_to_fortran

Typically, we see examples of poor saps who think that legacy Fortran codes must be converted to "modern" languages such as C++. Well, let's try to do the reverse! Let's try converting some "legacy" C++ code to modern Fortran. The example I will show is the Jacchia-Roberts atmosphere model from GMAT (General Mission Analysis Tool). This model is widely used for satellite orbit simulations to model satellite drag. There is no modern Fortran implementation publicly available as far as I can tell, so let's create one. And just to see what happens, I'll do this with assistance from AI, particularly Claude Sonnet (initially 4.5, but also trying 4.6) in GitHub Copilot. What could go wrong?

All About the Vibes

First, we start with the C++ files we want to convert. These files are at least 20 years old, with heritage going back even further. The main ones are:

  • JacchiaRobertsAtmosphere.cpp - the main implementation of the Jacchia-Roberts density model.
  • SolarFluxReader.cpp - the algorithm to read in a space weather file. There are a couple different file formats supported here, but I'm only going to worry about the CSSI one.
  • A1Mjd.cpp - Some time conversion routines. This will be important later when trying to diagnose some validation issues.

Using AI is a very interesting and also frustrating experience. It's like working with someone who is a genius, but is also an idiot and sometimes a compulsive liar. The initial conversion got me 80% of the way there but was subtle wrong in various ways. I started with a basic prompt ("Convert this C++ file into modern Fortran") and went from there. There was a lot of back and forth and manual interventions to fix little issues (syntax errors, etc.) In one instance, I tried to get it to identify the source of a bug, and it "thought" for 15 minutes until I finally just gave up and looked at the code myself and fixed it instantly. Sometimes, it would get fixated on one approach that wasn't correct, and I'd have to keep telling it to ignore that approach and try something else (e.g., I had to figure out the correct way to do something, and once I gave it more directions, it was fine and could do it). One thing that was annoying was that it kept trying to generate code from first principles rather than performing a straight up conversion of the C++ code to Fortran. I had to keep reminding it that what I wanted was a faithful translation. At one point, I switched to Sonnet 4.6 and it also found a couple of code typos that Sonnet 4.5 had generated.

The Result

See jacchia-roberts-fortran for the end result. The library provides a class that can be used to initialize the model with a given space weather file (or constant values can be specified). Then a method is provided for computing the density. An example use case is:

program example

use jacchia_roberts_module, only: jacchia_roberts_type
use jacchia_roberts_kinds,  only: ip, dp

implicit none

type(jacchia_roberts_type) :: jr
integer(ip) :: status
real(dp) :: density

! example inputs:
real(dp),parameter :: rad_earth = 6356.766_dp ! Earth polar radius (km)
character(len=*),parameter :: sw_file = 'data/SpaceWeather-All-v1.2.txt' ! space weather file to load
real(dp),parameter :: utc_mjd = 59215.5_dp ! MJD: Jan 1, 2021 12:00 UTC
real(dp),parameter :: alt_km = 200.0_dp ! geodetic altitude (km)
real(dp),parameter :: lat = 20.0_dp ! geodetic latitude (deg)
real(dp),dimension(3),parameter :: r = [7000.0_dp, 0.0_dp, 0.0_dp] ! spacecraft position vector (km)
real(dp),dimension(3),parameter :: sun = [1.0_dp, 0.0_dp, 0.0_dp] ! sun direction unit vector

! initialize the model:
call jr%initialize(rad_earth, filename=sw_file, status=status)

! compute the density:
density = jr%density(alt_km, r, sun_vector, lat, utc_mjd)

end program example

The idea is, first you would initialize the class, and then during your simulation, call the model to get the atmospheric density using the current epoch and state of the spacecraft. A flow chart of the process would look a little like this:

jacchia.mermaid

The resultant Fortran code is very clean compared to the C++ code. Consider this example (the rho_cor function). The original C++ code:

Real JacchiaRobertsAtmosphere::rho_cor(Real height, Real a1_time, Real geo_lat,
                                       GEOPARMS *geo)
{
   Real geo_cor, semian_cor, slat_cor, f, g, day_58, tausa, alpha;
   Real sin_lat, eta_lat;

   // Compute geomagnetic activity correction
   if (height < 200.0)
   {
      geo_cor = 0.012 * geo->tkp + 0.000012 * exp(geo->tkp);
   }
   else
   {
      geo_cor = 0.0;
   }

   // Compute semiannual variation correction
   f = (5.876e-7 * pow(height, 2.331) + 0.06328) *
      exp(-0.002868 * height);
   day_58 = (a1_time - 6204.5)/365.2422; // @todo - should this use GmatConstants value?
   tausa = day_58 + 0.09544 * (
         pow( 0.5*(1.0 + sin(2*GmatMathConstants::PI*day_58 +6.035)), 1.65 )  - 0.5);
   alpha = sin(4.0*GmatMathConstants::PI*tausa + 4.259);
   g = 0.02835 + (0.3817 + 0.17829 * sin(2*GmatMathConstants::PI*tausa + 4.137)) *
         alpha;
   semian_cor = f * g;

   // Compute seasonal latitudinal variation
   sin_lat = sin(geo_lat);
   eta_lat = sin(2.0*GmatMathConstants::PI*day_58 + 1.72) * sin_lat * fabs(sin_lat);
   slat_cor = 0.014 * (height - 90.0) * eta_lat *
      exp(-0.0013 * (height - 90.0) * (height - 90.0));

   return pow(10.0, geo_cor + semian_cor + slat_cor);
}

And the Fortran equivalent:

pure function rho_cor(height, utc_mjd, geo_lat, geo) result(correction)
    real(dp), intent(in) :: height !! Spacecraft altitude (km)
    real(dp), intent(in) :: utc_mjd !! UTC Modified Julian Date
    real(dp), intent(in) :: geo_lat !! Geodetic latitude of spacecraft (radians)
    type(geoparms_type), intent(in) :: geo !! Geomagnetic parameters
    real(dp) :: correction !! Correction factor (multiplicative)

    real(dp) :: geo_cor, semian_cor, slat_cor, f, g, day_58, tausa, alpha, sin_lat, eta_lat

    ! Compute geomagnetic activity correction
    if (height < 200.0_dp) then
        geo_cor = 0.012_dp * geo%tkp + 0.000012_dp * exp(geo%tkp)
    else
        geo_cor = 0.0_dp
    end if

    ! Compute semiannual variation correction
    f      = (5.876e-7_dp * height**2.331_dp + 0.06328_dp) * exp(-0.002868_dp * height)
    day_58 = (utc_mjd - 36204.0_dp) / 365.2422_dp ! years since Jan 1, 1958 midnight
    tausa  = day_58 + 0.09544_dp * &
            ((0.5_dp * (1.0_dp + sin(2.0_dp * PI * day_58 + 6.035_dp)))**1.65_dp - 0.5_dp)
    alpha  = sin(4.0_dp * PI * tausa + 4.259_dp)
    g      = 0.02835_dp + (0.3817_dp + 0.17829_dp * sin(2.0_dp * PI * tausa + 4.137_dp)) * alpha
    semian_cor = f * g

    ! Compute seasonal latitudinal variation
    sin_lat  = sin(geo_lat)
    eta_lat  = sin(2.0_dp * PI * day_58 + 1.72_dp) * sin_lat * abs(sin_lat)
    slat_cor = 0.014_dp * (height - 90.0_dp) * eta_lat * &
                exp(-0.0013_dp * (height - 90.0_dp)**2)

    correction = 10.0_dp**(geo_cor + semian_cor + slat_cor)

end function rho_cor

The main differences in the Fortran are:

  • The real kinds use the dp kind variable, allowing us to easily compile the code with a specified real kind (e.g., single, double, or quad precision). This is one of the neat features of Fortran, and how I write all my codes.
  • The crazy GSFC MJD (a1_time) is replaced with real MJD, so we had to modify that one constant (the epoch of Jan 1, 1958).
  • Normal syntactical differences: abs instead of fabs, no semicolons, ** operator instead of the pow function, then/endif instead of {} symbols, etc. The C++ code using (height - 90.0) * (height - 90.0) instead of (height - 90.0_dp)**2 is also kind of funny.

I find the Fortran version easier to read without the extra C++ clutter, but maybe it's just because i'm more used to it? This aspect of Fortran (it being easy to read) isn't always appreciated or discussed in these "language war" type discussions, but it's very real in my opinion, especially for code is that mostly math.

Here's an example output density plot (generated using my pyplot-fortran library) for three epochs near the max/min/average parts of the solar cycle:

density_profiles

Validation

One of the most interesting parts of the process was using AI to help validate the result. I had no unit tests to start with, but I had two sources of "truth":

  • GMAT itself, which I could run to generate density data for different altitudes, epochs, etc. and compare it to the Fortran results.
  • An older Fortran implementation of Jacchia-Roberts. This was a Fortran 77 implementation by Valdemir Carrara (Instituto Nacional de Pesquisas Espaciais, INPE) from the 80s, which I obtained from his website, which is no longer there (I archived the codes on GitHub in 2019 thinking I would do something with them one day, and now that day has arrived). This was also another AI opportunity, since the space weather component of this code was not preserved, so I had Copilot generate a wrapper so that it could call the modern Fortran module we had produced instead.

AI seems to get "unit test happy" sometimes, spewing out numerous long unit tests that do something simple and then prints up elaborate tables with unicode icons and paragraphs of text that tells you what it just tested. That seemed a bit excessive, and I ended up deleting many of the tests it generated, but kept a good number of them. Overall it was quite useful for helping to generate and evaluate tests. When tests failed (e.g., the new results didn't match the other two data sets) I would tell it to investigate to locate the source of the discrepancy, which it could sometimes do successfully, and sometimes not. The AI went on a flight of fancy for a while trying to figure out a timing discrepancy. It did help me debug it, but it couldn't quite figure it out. At one point I asked if it could go to GitHub and look at the full GMAT code that was there, it told me it could and provided an unconvincing reply suspiciously quickly that it had done so. I guess it was lying to me (Copilot I thought we were friends). I ended up downloading the extra file manually, putting it in the workspace, and directing the AI's attention to it, which seemed to help.

During the validation process, the AI did help me identify three possible issues with the original C++ code. I link the three bug reports I submitted here:

  1. PrepareKpData uses wrong row index for F10.7a - This is one that the AI detected that I would never have noticed in a million years. It looks like a copy/paste type bug but I'm not 100% sure if it's a real issue or not.
  2. Legacy UTC calculation used by JacchiaRobertsAtmosphere - This is one that I eventually noticed while trying to debug timing discrepancies during the validation process comparing the results from GMAT to the new version. GMAT (and other GSFC tools) use this crazy alternate definition of modified Julian date (MJD). So while trying to figure out how to convert what it was outputting in a real MJD, I realized they were using an approximation of UTC (it turns out the correct UTC value output from GMAT is slightly different from what is being used internally by the atmosphere model). It looks like something that was a holdover from the earlier tools, but I'm not sure if there's a good reason to keep using it. According to the git history when this file was first committed, the file was originally created for the GSS project in 1995 (I presume this is referring to the Generalized Support Software project at GSFC).
  3. Low-altitude Jacchia-Roberts bugs - This one is very interesting. There appear to be bugs in the GMAT code for low (<125 km) altitudes. Note that this part of the code is there but not used, since an exception is raised for this case, even though the Jacchia model should support the low altitude ranges (in Roberts' paper, he says his results "are identical" to Jacchia's original equations in the 90 - 125 km altitude regime). According to the comments and git history on SourceForge, this file was committed in 2004 and the source was the Windows Swingby code (Swingby was another legacy NASA tool that was one of the progenitors of STK/Astrogator). The roots function comments says it was converted from existing Fortran code (likely from GTDS, which included the Jacchia-Roberts model). However, the polynomial deflation technique was written in 1993 based on the "Numerical Recipes in C" book (note that Numerical Recipes talks about the need to "polish" a root found by this method, something not implemented in this code. Maybe that's where they went wrong?) Is it possible that Swingby had this same bug all those years ago? Or maybe my Fortran conversion is the buggy one? In any event, I replaced this algorithm in the Fortran version with an alternate root finding approach (based on the one in the INPE code) which seems to work fine. I also tested routines from polyroots-fortran and those gave the same results (I might end up just replacing this custom routine with the external dependency).

Legacy Schmegacy

spongebob

The earlier Fortran tool that some of this code was based on was the Goddard Trajectory Determination System (GTDS). This tool, like many Fortran tools, had a long heritage, but was thrown in the trash in the end, never modernized but replaced with C++ rewrites. Incidentally, GTDS and Swingby were developed by Computer Sciences Corporation (CSC), a company that existed from 1959 to 2017, which was cofounded by Roy Nutt, one of the members of the original team that created Fortran. So, now we have come full circle on this wheel of fire with a new modern Fortran port.

Conclusions

The end result of our AI-assisted C++ to Fortran conversion produced something that I think is likely quite useable. Overall I believe it did save me time compared to how long it would have taken me to do manually. But, getting this over the finish line definitely required manual intervention and domain knowledge. The process found a few potential bugs in the original code that need to be looked at by actual experts, since I don't trust the AI not a gaslight me with plausible-sounding reasons for them. Having the AI write the unit tests was also a huge time saver and help with debugging the implementation. In the end, for a task of this magnitude, I think AI can get you something like 95% of the way there, but then that last 5% needs to be completed and checked very carefully by somebody who more or less knows what they are doing. Blindly "vibe coding" is dangerous for technical applications such as this, since it's very easy for an AI to provide plausible looking procedures that are totally (or even worse, subtly) wrong.

Note: No AI was used to write the text of this article.

References

  1. Jacchia, L. G., "New Static Models of the Thermosphere and Exosphere with Empirical Temperature Profiles", Smithsonian Astrophysical Observatory Special Report No. 313, 1970.
  2. Roberts, C. E., Jr., "An Analytic Model for Upper Atmosphere Densities Based Upon Jacchia's 1970 Models", Celestial Mechanics, Vol. 4, pp. 368-377, 1971.
  3. GMAT: General Mission Analysis Tool
  4. Archive of legacy INPE atmosphere models github/@jacobwilliams
  5. Atmosphere Models [degenerateconic.com] 2021-10-24
  6. "Does the Programming Language Still Matter When AI Writes Your Code? I Tried Python and Came Back to C#" medium.com/@binnmti, Mar 16, 2026.
  7. J. Carrico & E. Fletcher, "Software Architecture and Use of Satellite Tool Kit’s Astrogator Module for Libration Point Orbit Missions", Libration Point Orbits and Applications, pp. 471-487 (2003)
  8. A. C. Long, et al., "Goddard Trajectory Determination System (GTDS) Mathematical Theory Revision 1", FDD/552-89/001, Computer Sciences Corporation, July 1989.

May 10, 2026

Fortran Compiler Updates (2026)

fortran2

Fortran compiler development continues, with regular updates across a range of compilers. Some of the recent updates are summarized below:

Intel

Intel has announced an update to their Fortran compiler (ifx), which is now at version 2026.0. ifx replaced the venerable ifort compiler a few years ago, and fully supports the Fortran 2018 standard and parts of the Fortran 2023 standard. The Fortran compiler is bundled as part of the Intel's oneAPI Toolkit. According to the release notes, there are only a few updates in this release:

  • Support for the leading zero edit mode, which allows a user to control the output of optional leading zeros in numeric outputs.
  • A coarray update related to NOTIFY_TYPE, which I don't understand. Maybe this is useful for somebody. Coarrays are the built-in MPI type feature of Fortran.
  • Support for C-style (a ? b : c) conditional expressions. This was a feature added to Fortran 2023, which is a profoundly weird fit for Fortran syntax, but it does allow a one-line short-circuited logical expression that wasn't otherwise possible before (the merge statement was the closest we had for that before but isn't quite the same).
  • Various new OpenMP 6.0 features and a new -qopenmp-threadprivate option.
  • On Windows, support for Microsoft Visual Studio 2026.

Presumably bugs have also been fixed, since the road to ifx adoption has been a little bumpy, with all new bugs that weren't present in ifort. Intel used to call out what bugs were fixed in their compilers, but they haven't done that in years, and users are left to figure out for themselves what might have been fixed.

GFortran

The GNU Project also just released GCC 16.1. According to the release notes the changes in GFortran are:

  • Coarrays using native shared memory multithreading on single node machines and handling Fortran 2018's TEAM feature.
  • Support the Fortran 2018 extensions to the import statement, the reduce intrinsic and the new generic statement.
  • The Fortran 2023 additions to the trigonometric functions are now supported (such as the sinpi intrinsic).
  • The Fortran 2023 split intrinsic subroutine is now supported and c_f_pointer now accepts an optional lower bound as an argument.
  • The -fexternal-blas64 option has been added to call external BLAS routines with 64-bit integer arguments for matmul.
  • Parameterized Derived Types (PDT) support is improved. GFortran's implementation of PDT's has been buggy and incomplete for years. PDT was a feature added in Fortran 2003 that looks like it might be useful for writing generic code, but in practice it is not very useful. Fortran 202y, the next major revision of the Fortran standard is slated to include better generics capabilities.

Honorable mentions

  • LFortran also continues to make steady progress towards its first beta release.
  • LLVM Flang development also continues, the latest release was 22.1.0. We still don't have Mac builds of this compiler available through conda, so I've never tested this one before. I've gotten this far without having to manually compile a Fortran compiler, and I'm not about to start now.

See also

Apr 26, 2025

Conda + Fortran

fortran-ai-2

The dark ages of confusion about where to get and how to install a Fortran compiler for your platform are over. In recent years, along with the overall renaissance of the Fortran ecosystem, with the conda package manager and the conda-forge package repository, we now have have easy access to all these free compilers for the three major platforms (Windows, Linux, and Mac):

Compiler Platform conda-forge Package Version
gfortran†† win-64 gfortran conda
gfortran†† linux-64 gfortran conda
gfortran†† osx-arm64 gfortran conda
flang‡ win-64 flang conda
flang‡ linux-64 flang conda
flang‡ osx-arm64 [Package not yet available] conda
ifx* win-64 ifx_win-64 conda
ifx* linux-64 ifx_linux-64 conda
lfortran† win-64 lfortran conda
lfortran† linux-64 lfortran conda
lfortran† osx-arm64 lfortran conda

Notes: †† gfortran is the venerable GNU Fortran compiler, the most mature open source Fortran compiler currently available. *ifx is Intel's replacement for ifort using an LLVM backend. It is free but not open source. They did not port it to the Mac ARM architecture, so that version of the compiler does not exist. On Windows, ifx still requires a Visual Studio installation to work. ‡flang (a.k.a. flang-new) is the official LLVM Fortran compiler. †LFortran, which also uses LLVM, is still under development and is currently an "alpha" product.

There is occasionally hand-wringing from some folks (e.g., SciPy, Los Alamos, etc.) that relying on Fortran compilers is somehow a risky proposition. But it has literally never been easier to install an up-to-date modern compiler for all platforms. Using conda, installing and trying out a Fortran compiler nowadays is as simple as this:

conda create --prefix ./env gfortran
conda activate ./env
gfortran --version

In addition, a lot of other tools that a Fortran programmer needs nowadays are also available via conda-forge. For example:

  • fpm -- Fortran Package Manager
  • ford -- Automatically generates Fortran documentation from comments within the code
  • fortls -- Fortran Language Server
  • fprettify -- Auto-formatter for modern fortran source code
  • findent -- Indent, relabel and convert Fortran sources

And you can also find other developer tools you may need including:

  • Python (and all the various Python libraries)
  • Cmake -- A Powerful Software Build System
  • Meson -- Open source build system meant to be both extremely fast, and, even more importantly, as user friendly as possible.
  • Ninja -- A small build system with a focus on speed.
  • git -- Distributed version control system

So, basically, you can get started with just one command:

conda create --channel conda-forge --prefix ./env gfortran fpm ford fortls fprettify findent cmake meson python git ninja

And it can all be installed in your user directory without requiring admin privileges on the machine (and if you want to remove it just delete that env folder and you are done). If you are using VSCode, you also will want to install the Modern Fortran extension. Then from the command palette, you can select "Python: Select Interpreter" and choose the env environment folder you created above. Then you are off to the races.

vscode-window

The conda distribution I recommend is miniforge. This is a version of conda that, by default, uses the conda-forge package channel rather than the commercial Anaconda channels which require a license to use. Note that conda itself is and has always been a free and open source project, so don't let your confused IT department get away with telling you it requires a paid license to use.

Another newer tool is pixi by the folks at prefix.dev. This is billed as a "fast software package manager built on top of the existing conda ecosystem". The syntax and some of the concepts are a little different, but it's an alternative way to install and use conda packages, and also works great.

See also

Jan 26, 2025

Root Finding

root3-300x295

*From [A FORTRAN Coloring Book](https://archive.org/details/9780262610261) by Roger Kaufman (1978)*

Finding the \(x\) where a scalar function \(f(x) = 0\) is a common problem in numerical programming. There are numerous applications to this. In orbital mechanics, for example, we may want to find the time at which a spacecraft reaches a certain altitude, or enters the Earth's shadow. For these cases, the independent variable is time, and the function \(f\) requires integration of the equations of motion of the spacecraft (for example, by using an IVP solver such as DDEABM). Integrating to an event is often referred to as a "G-stop" feature in these types of applications.

One way to solve this problem is using a derivative-free bracketing method. For these, only function evaluations are used, and the algorithm begins within a set of bounds (\([x_a, x_b]\)) that bracket the root (i.e., \(sign~f(x_a) \ne sign~f(x_b)\)). Over at the FORTRAN 77 Netlib graveyard, the classic zeroin root finding method can be found that implements such an algorithm, namely Brent's method by Australian mathematician Richard Brent from 1971.

roots-fortran-logo

In my roots-fortran library, I have modern Fortran implementations of this method and many others, as well as a lot of benchmark test cases. The library can be compiled for single (real32), double (real64), or quad (real128) precision reals. For real64 numbers and convergence tolerance \(\epsilon\) = 1.0e-15, the following table lists the number of function evaluations for various methods to converge to the root for various test functions:

\(f(x)\) \([x_a, x_b]\) \(x_{root}\) BS PE ITP TM BR AB MU CH
\((x - 1) e^{-x}\) \([0,1.5]\) 1.0 46 10 11 9 11 10 9 9
\(\cos x - x\) \([0,1.7]\) 0.7390851332151607 47 8 9 8 8 8 7 8
\(e^{x^2 + 7x - 30} - 1\) \([2.6,3.5]\) 3.0 44 20 17 18 12 31 11 11
\(x^3\) \([-0.5,1/3]\) 0.0 17 50 16 46 40 38 61 17
\(x^5\) \([-0.5,1/3]\) 0.0 11 53 12 20 19 39 35 11
\(\cos x - x^3\) \([0,4]\) 0.8654740331016144 48 13 11 13 14 15 10 11
\(x^3 - 1\) \([0.51,1.5]\) 1.0 46 10 11 9 10 9 7 8
\(x^2 ( x^2/3 + \sqrt{2} \sin x ) - \sqrt{3/18}\) \([0.1,1]\) 0.3994222917109682 47 12 17 10 12 12 9 10
\(x^3 + 1\) \([-1.8,0]\) -1.0 47 10 10 11 10 12 9 9
\(\sin((x-7.14)^3)\) \([7,8]\) 7.14 18 51 20 48 46 38 52 18
\(e^{(x-3)^5} - 1\) \([2.6,3.6]\) 3.0 11 55 12 24 26 41 29 11
\(e^{(x-3)^5} - e^{x-1}\) \([4,5]\) 4.267168304542125 44 53 45 11 14 58 20 14
\(\pi - 1/x\) \([0.05,5]\) \(1 / \pi\) 50 14 49 18 12 5 13 13
\(4 - \tan x\) \([0,1.5]\) 1.325817663668033 46 14 47 15 13 12 16 15
\(2 x e^{-5} - 2 e^{-5x} + 1\) \([0,10]\) 0.1382571550568241 52 13 16 13 13 11 13 13

The methods are: BS = Bisection, PE = Pegasus, ITP, TM = TOMS 748, BR = Brent, AB = Anderson Bjorck, MU = Muller, and CH = Chandrupatla.

There seems to be cottage industry of creating slight variations of this type of method. There are any number of recent papers on this topic, all claiming some improvement. In reality, a lot of the newer methods really aren't great. An obvious trick is a choose functions where your method behaves well. Another is to only compare against very simple methods (such as bisection or regula falsi, which are known to be inefficient). Finally, the most devious trick is to list the number of iterations of the method, rather than the number of function evaluations, which is basically a useless comparison that obscures the real cost for methods that have multiple function evaluations per iteration.

The Brent method is still one of the best performers. The Chandrupatla method from 1997 is also quite good. For the full set of test cases, see the roots-fortran git repo.

Minimization methods

A related type of algorithm are derivative-free minimization methods. The FMIN function at Netlib is based on Brent's original localmin algorithm (modern version here). The PRIMA library implements modern versions of M.J.D. Powell's derivative-free minimizers for multidimensional functions.

See also

Nov 24, 2023

Fortran 2023 Unleashed

fortran-2023

Fortran 2023 (ISO/IEC 1539-1:2023) has been officially published. This replaces the previous standard (Fortran 2018). In a previous post I listed the updates that are in this release (then called "Fortran 202x"). It will be a while before we have fully-compliant Fortran 2023 compilers. In the latest update to the Intel Fortran Compiler, they have started to add a few minor things.

Fortran is the oldest surviving programming language still in common use. Originally developed in 1957, first standardized in 1966, and continuously updated since that time, Fortran remains at the heart of scientific and high-performance computing.

The next standard (unofficially called Fortran 202y) is also already in work.

fortran-2023

See also

Oct 12, 2022

A Modern Fortran Scientific Programming Ecosystem

Scientific Programming Ecosystem

Image created with the assistance of NightCafe Creator.

Historically, large general-purpose libraries have formed the core of the Fortran scientific ecosystem (e.g., SLATEC, or the various PACKS). Unfortunately, as I have mentioned here before, these libraries were written in FORTRAN 77 (or earlier) and remained unmodified for decades. The amazing algorithms continued within them imprisoned in a terrible format that nobody wants to deal with anymore. At the time they were written, they were state of the art. Now they are relics of the past, a reminder of what might have been if they had continued to be maintained and Fortran had continued to remain the primary scientific and technical programming language.

Over the last few years, I've managed to build up a pretty good set of modern Fortran libraries for technical computing. Some are original, but a lot of them include modernized code from the libraries written decades ago. The codes still work great (polyroots-fortran contains a modernized version of a routine written 50 years ago), but they just needed a little bit of cleanup and polish to be presentable to modern programmers as something other than ancient legacy to be tolerated but not well maintained (which is how Fortran is treated in the SciPy ecosystem).

Here is the list:

Catagory Library Description Release
Interpolation bspline-fortran 1D-6D B-Spline Interpolation GitHub release
Interpolation regridpack 1D-4D linear and cubic interpolation GitHub release
Interpolation finterp 1D-6D Linear Interpolation GitHub release
Interpolation PCHIP Piecewise Cubic Hermite Interpolation Package GitHub release
Plotting pyplot-fortran Make plots from Fortran using Matplotlib GitHub release
File I/O json-fortran Read and write JSON files GitHub release
File I/O fortran-csv-module Read and write CSV Files GitHub release
Optimization slsqp SLSQP Optimizer GitHub release
Optimization fmin Derivative free 1D function minimizer GitHub release
Optimization pikaia Pikaia Genetic Algorithm GitHub release
Optimization simulated-annealing Simulated Annealing Algorithm GitHub release
One Dimensional Root-Finding roots-fortran Roots of continuous scalar functions of a single real variable, using derivative-free methods GitHub release
Polynomial Roots polyroots-fortran Root finding for real and complex polynomial equations GitHub release
Nonlinear equations nlesolver-fortran Nonlinear Equation Solver GitHub release
Ordinary Differential Equations dop853 An explicit Runge-Kutta method of order 8(5,3) GitHub release
Ordinary Differential Equations ddeabm DDEABM Adams-Bashforth algorithm GitHub release
Numerical Differentiation NumDiff Numerical differentiation with finite differences GitHub release
Numerical integration quadpack Modernized QUADPACK Library for 1D numerical quadrature GitHub release
Numerical integration quadrature-fortran 1D-6D Adaptive Gaussian Quadrature GitHub release
Random numbers mersenne-twister-fortran Mersenne Twister pseudorandom number generator GitHub release
Astrodynamics Fortran-Astrodynamics-Toolkit Modern Fortran Library for Astrodynamics GitHub release
Astrodynamics astro-fortran Standard models used in fundamental astronomy GitHub release

dependencies

*Example of module dependencies in a Fortran application. This is for the [Halo solver](https://jacobwilliams.github.io/halo/lists/modules.html).*

All of these libraries satisfy my requirements for being part of a modern Fortran scientific ecosystem:

  • All are written in a modern Fortran style (free-form, modules, no obsolete constructs such as gotos or common blocks, etc.) All legacy code has been modernized.
  • They are all usable with the Fortran Package Manager. A couple of previous posts here and here show how easy it is to do this with a one-line addition to your FPM manifest file.
  • The real kind (single, double, or quad precision) is selectable via a preprocessor directive.
  • All libraries are available in git repositories on GitHub and contributions are welcome. Every commit is unit tested using GitHub CI.
  • All the sourcecode is documented online using FORD.
  • All have permissive licenses (e.g., BSD-3) so you can use them however you want.

Fortran has many advantages for scientific computing. It's fast, standard, statically typed, compiled, stable, has a nice array syntax, and includes object oriented programming and native parallelism. It is great for technical and numerical codes that need to run fast and are intended to be used for decades. The libraries listed above will not stop working in a few years. An extremely complicated Fortran application can be recompiled with just a Fortran compiler. You cannot say the same for anything written in the Python scientific ecosystem, which is a Frankenstein hybrid of a scripting language hacked together with a pile of C/C++/Fortran libraries compiled by somebody else. Good luck trying to run Python you write now 20 years from now (or trying to run something written 20 years ago). Fortran is a simple and stable foundation upon which to build our scientific software, Python is not. Having readily available modern libraries along with recent improvements in the Fortran tooling and ecosystem should only serve to make Fortran more appealing in this area.

See also

Jul 31, 2022

Near Rectilinear Halo Orbits

nrhos

*Some Earth-Moon NRHOs. From [Williams et al, 2017](https://www.researchgate.net/publication/322526659_Targeting_Cislunar_Near_Rectilinear_Halo_Orbits_for_Human_Space_Exploration).*

I have mentioned the Circular Restricted Three-Body Problem (CR3BP) here a couple of times. Another type of periodic CR3BP orbit is the "near rectilinear" halo orbit (NRHO). These orbits are just the continuation of the family of "normal" halo orbits until they get very close to the secondary body [1]. Like all halo orbits, there are "northern" and "southern" families (see image above). NASA chose a lunar L2 NRHO (southern family, which spends more time over the lunar South pole) as the destination orbit for the Deep Space Gateway [2]. The CAPSTONE mission [3] successfully launched on June 28, 2022, and is currently en route to the Gateway NRHO. [4-5]

Computing an NRHO

A basic NRHO in the CR3BP can be computed just like a "normal" halo orbit. (see this and this earlier post). In Reference [2] we described methods for generating long-duration NRHOs in a realistic force model. The procedure involves a multiple shooting method using the CR3BP solution as an initial guess replicated for multiple revs of the orbit. Then a simple nonlinear solver is used to impose state and time continuity for the full orbit.

All we need to start the process is an initial guess. Appendix A.1 in Reference [6] lists a table of CR3BP Earth-Moon L2 halo orbits. An example NRHO from this table is shown here:

Parameter Value
\(l^{*}\) 384400.0
\(t^{*}\) 375190.25852
Jacobi constant 3.0411
period 1.5872714606
\(x_0\) 1.0277926091
\(z_0\) -0.1858044184
\(\dot y_0\) -0.1154896637

In this table, the three state parameters (\(x_0\), \(z_0\), and \(\dot y_0\)) are given in the canonical normalized CR3BP system, with respect to the Earth-Moon barycenter. The distance and time normalization factors (\(l^{*}\) and \(t^{*}\)) can be used to convert them into normal distance and time units (km and sec).

The process is fairly straightforward, and is demonstrated in a new tool, which I call Halo. It is written in Modern Fortran. The Halo tool uses the initial CR3BP state to generate a long-duration orbit in the real ephemeris model. Halo doesn't use the CR3BP equations of motion, since we will be integrating in the real system, using the actual ephemeris of the Earth and Moon, and so we use the real units. See Reference [7] for some further details about an earlier version of this code. Halo is built upon a number of open source Fortran libraries, and the main components of the tool are:

  • General astrodynamics routines, including: ephemeris of the Sun, Earth, and Moon, a spherical harmonic lunar gravity model, state transformations, etc. These are all provided by the Fortran Astrodynamics Toolkit.
  • A numerical integration method. I'm using DDEABM, which is an Adams method. I like this method, which has a long heritage and seems to have a good balance of speed and accuracy for these kinds of astrodynamics problems.
  • A way to compute derivatives, which are needed by the solver for the Jacobian. For this problem, finite-difference numerical derivatives are good enough, so I'm using my NumDiff library. Since the problem ends up being quite sparse, we can use the NumDiff options to quickly compute the Jacobian matrix by taking into account the sparsity pattern.
  • A nonlinear numerical solver. I'm using my nlsolver-fortran library. The problem being solved is "underdetermined", meaning there are more free variables than there are constraints. Nlsolver-Fortran can solve these problems just fine using a basic Newton-type minimum norm method. In astrodynamics, you will usually see this type of method called a "differential corrector".

Halo makes use of the amazing Fortran Package Manager (FPM) for dependency management and compilation. In the fpm.toml manifest file, all we have to do is specify the dependencies like so:

[dependencies]
fortran-astrodynamics-toolkit = { git = "https://github.com/jacobwilliams/Fortran-Astrodynamics-Toolkit", tag = "0.4" }
NumDiff = { git = "https://github.com/jacobwilliams/NumDiff", tag = "1.5.1" }
ddeabm = { git = "https://github.com/jacobwilliams/ddeabm", tag = "3.0.0" }
json-fortran = { git = "https://github.com/jacobwilliams/json-fortran", tag = "8.2.5" }
bspline-fortran = { git = "https://github.com/jacobwilliams/bspline-fortran", tag = "6.1.0" }
pyplot-fortran = { git = "https://github.com/jacobwilliams/pyplot-fortran", tag = "3.2.0" }
fortran-search-and-sort = { git = "https://github.com/jacobwilliams/fortran-search-and-sort", tag = "1.0.0" }
fortran-csv-module = { git = "https://github.com/jacobwilliams/fortran-csv-module", tag = "1.3.0" }
nlesolver-fortran = { git = "https://github.com/jacobwilliams/nlesolver-fortran", tag = "1.0.0" }
argv-fortran = { git = "https://github.com/jacobwilliams/argv-fortran", tag = "1.0.0" }

libs-for-halo

FPM will download the specified versions of everything and build the tool with a single command like so:

fpm build --profile release

This is really a game changer for Fortran. Before FPM, poor Fortran users had no package manager at all. A common anti-pattern was to manually download code from somewhere like Netlib and then manually add the files to your repo and manually edit your makefiles to incorporate it into your compile process. This is literally what SciPy has done, and now they have forks of all their Fortran code that just lives inside the SciPy monorepo, with no way to distribute it upstream to the original library source, or downstream to other Fortran codes that might want to use it. FPM makes it all just effortless to specify dependencies (and update the version numbers if the upstream code changes) and finally allows and encourages Fortran users to develop a modern system of interdependent libraries with sane and modern version control practices.

Example

Using the initial guess from the table above, we can use Halo to compute a ballistic NRHO for 50 revs (about 344 days for this example) in the real ephemeris model. The 50 rev problem has 1407 control variables and 1200 equality constraints. Convergence takes about 31 seconds (6 iterations of the solver) on my M1 MacBook Pro, complied using the Intel Fortran compiler:

Iteration number norm2(Constraint violation vector)
1 0.2936000870578949
2 0.1554308242228055
3 0.0611200979653433
4 0.0052758762761737
5 0.0000031933098398
6 0.0000000035186120

nrho_solution

*50 Rev NRHO Solution. Visualized here in the Moon-centered Earth-Moon rotating frame.*

There's no parallelization currently being used here, so there are some opportunities to speed it up even more. In Reference [2] we also show how the above procedure can be used to kick off a "receding horizon" approach to compute even longer duration NRHOs that include very small correction burns. This is the process that was used to generate the Gateway orbit [4-5], and the Halo program could be updated to do that as well. But that is a post for another time.

See also

  1. K. C. Howell & J. V. Breakwell, "Almost rectilinear halo orbits", Celestial mechanics volume 32, pages 29--52 (1984)
  2. J. Williams, D. E. Lee, R. J. Whitley, K. A. Bokelmann, D. C. Davis, and C. F. Berry. "Targeting cislunar near rectilinear halo orbits for human space exploration", AAS 17-267
  3. What is CAPSTONE?, NASA, Apr 29, 2022
  4. D. E. Lee, "Gateway Destination Orbit Model: A Continuous 15 Year NRHO Reference Trajectory", August 20, 2019
  5. D. E. Lee, R. J. Whitley and C. Acton, "Sample Deep Space Gateway Orbit, NAIF Planetary Data System Navigation Node", NASA Jet Propulsion Laboratory, July 2018.
  6. E. M. Z. Spreen, "Trajectory Design and Targeting For Applications to the Exploration Program in Cislunar Space", PhD Dissertation, Purdue University, 2021
  7. J. Williams, R. D. Falck, and I. B. Beekman, "Application of Modern Fortran to Spacecraft Trajectory Design and Optimization", 2018 Space Flight Mechanics Meeting, 8–12 January 2018, AIAA 2018-145.

Jun 19, 2022

D1MACH Re-Revisited

slatec

[NightCafe](https://creator.nightcafe.studio/) AI generated image for "old computers"

The classic Fortran routines r1mach (for single precision real numbers), d1mach (for double precision real numbers), and i1mach (for integers) were originally written in the mid-1970s for the purpose of returning basic machine or operating system dependent constants in order to provide portability. Typically, these routines had a bunch of commented out DATA statements, and the user was required to uncomment out the ones for their specific machine. The original versions included constants for the following systems:

Over the years, various versions of these routines have been created:

In the Fortran 90 versions, the original routines were modified to use various intrinsic functions available in Fortran 90. However, some hard-coded values remained in i1mach. With the advent of more recent standards, it is now possible to provide modern and completely portable versions which are described below. Starting with the Fortran 90 versions, the following changes were made:

  • The routines are now in a module. We declare two module parameters which will be used in the routines:
integer, parameter :: sp = kind(1.0)    ! single precision kind
integer, parameter :: dp = kind(1.0d0)  ! double precision kind

Of course, putting them in a module means that they are not quite drop-in replacements for the old routines. If you want to use this module with legacy code, you'd have to update your code to add a use statement. But that's the way it goes. Nobody should be writing Fortran anymore that isn't in a module, so I'm not going to encourage that.

  • The comment strings have been updated to Ford-style. I find the old SLATEC comment block style extremely cluttered. Various cosmetic changes were also made (e.g., changing the old .GE. to >=, etc.)

  • All three routines were made pure. The error stop construct was used for the out of bounds input condition (Fortran 2008 allows error stop in pure procedures).

  • New intrinsic constants available in the Fortran 2008 standard were employed (see below).

The new routines can be found on GitHub. They are shown below without the comment blocks.

The new routines

The new d1mach routine is:

D1MACH

  pure real(dp) function d1mach(i)

    integer, intent(in) :: i

    real(dp), parameter :: x = 1.0_dp
    real(dp), parameter :: b = real(radix(x), dp)

    select case (i)
    case (1); d1mach = b**(minexponent(x) - 1) ! the smallest positive magnitude.
    case (2); d1mach = huge(x)                 ! the largest magnitude.
    case (3); d1mach = b**(-digits(x))         ! the smallest relative spacing.
    case (4); d1mach = b**(1 - digits(x))      ! the largest relative spacing.
    case (5); d1mach = log10(b)
    case default
        error stop 'Error in d1mach - i out of bounds'
    end select

  end function d1mach

This is mostly just a reformatting of the Fortran 90 routine. We make x and b parameters, and use error stop for the error condition rather than this monstrosity:

          WRITE (*, FMT = 9000)
 9000     FORMAT ('1ERROR    1 IN D1MACH - I OUT OF BOUNDS')
          STOP

R1MACH

The updated R1MACH is similar, except the real values are real(sp) (default real, or single precision):

  pure real(sp) function r1mach(i)

    integer, intent(in) :: i

    real(sp), parameter :: x = 1.0_sp
    real(sp), parameter :: b = real(radix(x), sp)

    select case (i)
    case (1); r1mach = b**(minexponent(x) - 1) ! the smallest positive magnitude.
    case (2); r1mach = huge(x)                 ! the largest magnitude.
    case (3); r1mach = b**(-digits(x))         ! the smallest relative spacing.
    case (4); r1mach = b**(1 - digits(x))      ! the largest relative spacing.
    case (5); r1mach = log10(b)
    case default
      error stop 'Error in r1mach - i out of bounds'
    end select

  end function r1mach

I1MACH

The new i1mach routine is:

  pure integer function i1mach(i)

    integer, intent(in) :: i

    real(sp), parameter :: x = 1.0_sp
    real(dp), parameter :: xx = 1.0_dp

    select case (i)
    case (1);  i1mach = input_unit
    case (2);  i1mach = output_unit
    case (3);  i1mach = 0  ! Punch unit is no longer used
    case (4);  i1mach = error_unit
    case (5);  i1mach = numeric_storage_size
    case (6);  i1mach = numeric_storage_size / character_storage_size
    case (7);  i1mach = radix(1)
    case (8);  i1mach = numeric_storage_size - 1
    case (9);  i1mach = huge(1)
    case (10); i1mach = radix(x)
    case (11); i1mach = digits(x)
    case (12); i1mach = minexponent(x)
    case (13); i1mach = maxexponent(x)
    case (14); i1mach = digits(xx)
    case (15); i1mach = minexponent(xx)
    case (16); i1mach = maxexponent(xx)
    case default
      error stop 'Error in i1mach - i out of bounds'
    end select

  end function i1mach

In this one, we make heavy use of constants now in the intrinsic iso_fortran_env module, including input_unit, output_unit, error_unit, numeric_storage_size, and character_storage_size. In the Fortran 90 code, the values for I1MACH(1:4) and I1MACH(6) were totally not portable. This new one is now (almost) entirely portable.

The only remaining parameter that cannot be expressed portably is I1MACH(3), the standard punch unit. If you look at the original i1mach.f from SLATEC, there were a range of values of this parameter used by various machines (e.g. 102 for the Cray, or 7 for the IBM 360/370). Hopefully, this is not something that anybody needs nowadays, so we leave it set to 0 in the updated routine. If the Fortran committee ever adds punch_unit to iso_fortran_env, then we can update it then, and this routine will finally be finished, and the promise of a totally portable machine constants routine will finally be fulfilled.

Another interesting thing to note are I1MACH(7) (which is RADIX(1)) and I1MACH(10) (which is RADIX(1.0)). There was no I1MACH parameter for RADIX(1.0D0). There were machine architectures where I1MACH(7) /= I1MACH(10). For example, the Data General Eclipse S/200 had I1MACH(7)=2 and I1MACH(10)=16. But, it seems unlikely that there would ever be an architecture where the radix would be different for different real kinds. So maybe we are safe?

Results

On my laptop (Apple M1) with gfortran, the three routines produce the following values:

i1mach( 1) =                5
i1mach( 2) =                6
i1mach( 3) =                0
i1mach( 4) =                0
i1mach( 5) =               32
i1mach( 6) =                4
i1mach( 7) =                2
i1mach( 8) =               31
i1mach( 9) =       2147483647
i1mach(10) =                2
i1mach(11) =               24
i1mach(12) =             -125
i1mach(13) =              128
i1mach(14) =               53
i1mach(15) =            -1021
i1mach(16) =             1024

r1mach( 1) =             1.17549435E-038    (            800000 )
r1mach( 2) =             3.40282347E+038    (          7F7FFFFF )
r1mach( 3) =             5.96046448E-008    (          33800000 )
r1mach( 4) =             1.19209290E-007    (          34000000 )
r1mach( 5) =             3.01030010E-001    (          3E9A209B )

d1mach( 1) =   2.22507385850720138E-0308    (    10000000000000 )
d1mach( 2) =   1.79769313486231571E+0308    (  7FEFFFFFFFFFFFFF )
d1mach( 3) =   1.11022302462515654E-0016    (  3CA0000000000000 )
d1mach( 4) =   2.22044604925031308E-0016    (  3CB0000000000000 )
d1mach( 5) =   3.01029995663981198E-0001    (  3FD34413509F79FF )

References

Jun 17, 2022

Quadrature Quandary

slatec

In today's episode of "What where the Fortran dudes in the 1980s thinking?": consider the following code, which can be found in various forms in the two SLATEC quadrature routines dgaus8 and dqnc79:

K = I1MACH(14)
ANIB = D1MACH(5)*K/0.30102000D0
NBITS = ANIB

Note that the "mach" routines (more on that below) return:

  • I1MACH(10) = B, the base. This is equal to 2 on modern hardware.
  • I1MACH(14) = T, the number of base-B digits for double precision. This will be 53 on modern hardware.
  • D1MACH(5) = LOG10(B), the common logarithm of the base.

First off, consider the 0.30102000D0 magic number. Is this supposed to be log10(2.0d0) (which is 0.30102999566398120)? Is this a 40 year old typo? It looks like they ended it with 2000 when they meant to type 3000? In single precision, log10(2.0) is 0.30103001. Was it just rounded wrong and then somebody added a D0 to it at some point when they converted the routine to double precision? Or is there some deep reason to make this value very slightly less than log10(2.0)?

But it turns out, there is a reason!

If you look at the I1MACH routine from SLATEC you will see constants for computer hardware from the dawn of time (well, at least back to 1975. It was last modified in 1993). The idea was you were supposed to uncomment the one for the hardware you were running on. Remember how I said IMACH(10) was equal to 2? Well, that was not always true. For example, there was something called a Data General Eclipse S/200, where IMACH(10) = 16 and IMACH(14) = 14. Who knew?

So, if we compare two ways to do this (in both single and double precision):

! slatec way:
 anib_real32  = log10(real(b,real32))*k/0.30102000_real32;   nbits_real32  = anib_real32
 anib_real64  = log10(real(b,real64))*k/0.30102000_real64;   nbits_real64  = anib_real64

! naive way:
 anib_real32_alt  = log10(real(b,real32))*k/log10(2.0_real32);   nbits_real32_alt  = anib_real32_alt
 anib_real64_alt  = log10(real(b,real64))*k/log10(2.0_real64);   nbits_real64_alt  = anib_real64_alt

! note that k = imach(11) for real32, and imach14 for real64

We see that, for the following architectures from the i1mach routine, the naive way will give the wrong answer:

  • BURROUGHS 5700 SYSTEM (real32: 38 instead of 39, and real64: 77 instead of 78)
  • BURROUGHS 6700/7700 SYSTEMS (real32: 38 instead of 39, and real64: 77 instead of 78)

The reason is because of numerical issues and the fact that the real to integer assignment will round down. For example, if the result is 38.99999999, then that will be rounded down to 38. So, the original programmer divided by a value slightly less than log10(2) so the rounding would come out right. Now, why didn't they just use a rounding function like ANINT? Apparently, that was only added in Fortran 77. Work on SLATEC began in 1977, so likely they started with Fortran 66 compilers. And so this bit of code was never changed and we are still using it 40 years later.

Numeric inquiry functions

In spite of most or all of the interesting hardware options of the distant past no longer existing, Fortran is ready for them, just in case they come back! The standard includes a set of what are called numeric inquiry functions that can be used to get these values for different real and integer kinds:

Name Description
BIT_SIZE The number of bits in an integer type
DIGITS The number of significant digits in an integer or real type
EPSILON Smallest number that can be represented by a real type
HUGE Largest number that can be represented by a real type
MAXEXPONENT Maximum exponent of a real type
MINEXPONENT Minimum exponent of a real type
PRECISION Decimal precision of a real type
RADIX Base of the model for an integer or real type
RANGE Decimal exponent range of an integer or real type
TINY Smallest positive number that can be represented by a real type

So, the D1MACH and I1MACH routines are totally unnecessary nowadays. Modern versions that are simply wrappers to the intrinsic functions can be found here, which may be useful for backward compatibility purposes when using old codes. I tend to just replace calls to these functions with calls to the intrinsic ones when I have need to modernize old code.

Updating the old code

In modern times, we can replace the mysterious NBITS code with the following (maybe) slightly less mysterious version:

 integer,parameter :: nbits = anint(log10(real(radix(1.0_wp),wp))*digits(1.0_wp)/log10(2.0_wp))

 ! on modern hardware, nbits is: 24 for real32
 !                               53 for real64
 !                              113 for real128

Where:

  • wp is the real precision we want (e.g., real32 for single precision, real64 for double precision, and real128 for quad precision).
  • radix(1.0_wp) is equivalent to the old I1MACH(10)
  • digits(1.0_wp) is equivalent ot the old I1MACH(14)

Thus we don't need the "magic" number anymore, so it's time to retire it. I plan to make this change in the refactored Quadpack library, where these routines are being cleaned up and modernized in order to serve for another 40 years. Note that, for modern systems, this equation reduces to just digits(1.0_wp), but I will keep the full equation, just in case. It will even work on the Burroughs, just in case those come back in style.

quadpack

Final thought

If you search the web for "0.30102000" you can find various translations of these old quadrature routines. My favorite is this Java one:

//  K = I1MACH(14) --- SLATEC's i1mach(14) is the number of base 2
//  digits for double precision on the machine in question.  For
//  IEEE 754 double precision numbers this is 53.

      k = 53;

//  r1mach(5) is the log base 10 of 2 = .3010299957

//      anib = .3010299957*k/0.30102000E0;

//      nbits = (int)(anib);

      nbits = 53;

Great job guys!

Acknowlegments

Special thanks to Fortran-lang Discourse users @urbanjost, @mecej4, and @RonShepard in this post for helping me get to the bottom of the mysteries of this code.

See also

Mar 27, 2022

The New Features of Fortran 202x

new-features-of-fortran2020x

The glacially slow pace of Fortran language development continues! From the smoke-filled room of the Fortran Standards Committee, the next Fortran standard is coming along. It has not yet been given an official name but is currently referred to as "Fortran 202x" (so hopefully we will see it before the end of this decade). Recall that Fortran 90 was originally referred to as "Fortran 198x".

A document (from John Reid) describing the new features is available here and is summarized below:

  • The limit on line length has been increased to ten thousand characters, and the limit on total statement length (with all continuation lines) has been increased to a million characters.
  • Allocatable-length character variables were added in Fortran 2003, but were ignored by all intrinsic procedures, until now! Better late than never, I suppose. In Fortran 202x, intrinsic routines that return strings can now be passed an allocatable string and it will be auto-allocated to the correct size. This is actually a pretty great addition, since it fixes an annoyance that we've had to live with for 20 years. Of course, what we really need is a real intrinsic string class. Maybe in 20 more years we can have that.
  • typeof and classof. No idea what these are for. Probably a half-baked generics feature? What we really need is a real generics feature. Since that is in work for Fortran 202y I don't know why these have been added now. Probably we will discover in 10 years that these aren't good for much, but will be stuck with them until the end of time (see parameterized derived types).
  • Conditional expressions. This adds the dreadful C-style (a ? b : c) conditional expression to Fortran. Somebody thought this was a good idea? This abomination can also be passed to an optional procedure argument, with a new .nil. construct to signify not present.
  • Improvements to the half-baked binary, octal, and hexadecimal constants.
  • Two string parsing routines (split and tokenize) were added. These are basically Fortran 90 style string routines that I guess are OK, but why bother? Or why limit to just these two? See above comment about adding a real string class.
  • Trig functions that accept input in degrees have been added (e.g. sind, cosd, etc). These are already commonly available as extensions in many compilers, so now they will just be standardized.
  • Half revolution trig function have also been added (acospi, asinpi, etc.). I have no idea what these are good for.
  • A selected_logical_kind function was added. This was a missing function that goes along with the selected_real_kind and selected_int_kind functions that were added in Fortran 2003. Logical type parameters (e.g., logical8, logical16, etc. were also added to the intrinsic iso_fortran_env module).
  • Some updates to the system_clock intrinsic.
  • Some new functions in the ieee_arithmetic module for conformance with the 2020 IEEE standard.
  • Update to the c_f_pointer intrinsic procedure to allow for specifying the lower bound.
  • New procedures were added to iso_c_binding for conversion between Fortran and C strings. This is nice, since everyone who ever used this feature had to write these themselves. So, now they will be built in.
  • A new AT format code, which is like A, but trims the trailing space from a string. Another nice little change that will save a lot of trim() functions.
  • Opening a file now includes a new option for setting the leading zeros in real numbers (print them or suppress them).
  • Public namelists can now include private variables. Sure, why not?
  • Some updates for coarrays (coarrays are the built-in MPI like component of Fortran).
  • A new simple procedure specification. A simple procedure is sort of a super pure procedure that doesn't reference any variables outside of the procedure. Presumably, this would allow some additional optimizations from the compiler.
  • A new "multiple subscript" array feature. The syntax for this is just dreadful (using the @ character in Fortran for the first time).
  • Integer arrays can now be used to specify the rank and bounds of an array. I tried to read the description of this, but don't get it. Maybe it's useful for something.
  • Can now specify the rank of a variable using an integer constant. This is nice, and makes the rank parts of the language a little more consistent with the other parts. Again, this is something that is cleaning up a half-baked feature from a previous standard.
  • Reduction specifier for do concurrent, which is putting more OpenMP-like control into the actual language (but just enough so people will still complain that is isn't enough).
  • enumeration types have been added for some reason. In addition, the half-baked Fortran 2003 enum has been extended so it can now be used to create a new type. I'm not entirely sure why we now have two different ways to do this.

fortran-committee

So, there you have it. There are some nice additions that current users of Fortran will appreciate. It will likely be years before compilers implement all of this though. Fortran 202x will be the 3rd minor update of Fortran since Fortran 2003 was released 20 years ago. Unfortunately, there really isn't much here that is going to convince anybody new to start using Fortran (or to switch from Python or Julia). What we need are major additions to bring Fortran up to speed where it has fallen behind (e.g., in such areas as generics, exception handling, and string manipulation). Or how about some radical upgrades like automatic differentiation or built-in units for variables?

References

Next → Page 1 of 15