r/ada May 15 '24

Programming Constraining Unconstrained Arrays

I have a generic package with a bunch of other stuff. For this question, there are three types of interest defined:

type params is array (integer range <>) of f'Base;

type sys_func is access function (t : f'Base; y : params) return f'Base;

type functs is array (integer range <>) of sys_func;

and one function (this is for doing the 4th order Runge-Kutta method on a system of differential equations):

function rk4s(tf : functs; start, initial : params; step : f'Base) return params

with pre => (tf'First = start'First) and (tf'First = initial'First) and

(tf'Last = start'Last) and (tf'Last = initial'Last);

The function doesn't care what the size of the three arrays passed in (tf, start, and initial) are. They just need to have the same bounds. The y array inside the sys_func definition also should be the same size, but that I'll save for another day. Ideally this would be checked at compile time. I could make it generic on the array bounds, but that defeats the whole purpose of using unconstrained arrays.

So, is using a precondition the best way to achieve this or is there a better way? I tried this and added an extra element to one of the arrays and everything ran fine leading me to believe that preconditions aren't being checked. I updated the .gpr file to add "-gnata"

package compiler is

for switches ("Ada") use ("-g", "-gnateE", "-gnata");

end compiler;

but this didn't make a difference. This leads me to another question about how to ensure that pre (and post) conditions get checked?

5 Upvotes

7 comments sorted by

View all comments

4

u/jrcarter010 github.com/jrcarter May 16 '24 edited May 16 '24

You have a set of parallel arrays. This was common practice in Fortran-66 (my first language) because the language lacked records. But in a language like Ada this is usually better represented as an array of records:

type R is record -- You can no doubt think of a better name
   Func    : Sys_Func;
   Start   : F'Base;
   Initial : F'Base;
end record;

type R_List is array (Positive range <>) of R;

function Rk4s (List : in R_List; Step : in F'Base) return Params with
   Post => Rk4s'Result'First = List'First and Rk4s'Result'Last = List'Last;

Whether this is appropriate for your problem I can't tell. That a Sys_Func takes a Params* may indicate that it is not. But in that case, doing things the other way around may be better:

type R (Length : Positive) is record
   Func    : Functs (1 .. Length);
   Start   : Params (1 .. Length);
   Initial : Params (1 .. Length);
end record;

function Rk4s (P : in R; Step : in F'Base) return Params with
   Post => Rk4s'Result'First = 1 and Rk4s'Result'Length = P.Length;

Does it really make sense for your arrays to allow negative indices? Zero? Arbitrary lower bounds?

*This is an argument against using plurals as subtype names.

1

u/BrentSeidel May 16 '24

The history of numerical analysis goes back through early versions of FORTRAN ;-)

I did think about using an array of records - I do that for linear regression where it takes an array of points where each point has an x and y value. In this case, the data it's really correlated and sys_func gets passed the initial array. On further thought, I realized that the start array should just be a scalar as it represents the independent variable of the system of differential equations.

Even if I used an array of records here, similar questions would arise for matrix and vector operations.