Organizing many inputs for iterative function calls

I am working on my first package (a basic charge simulator for electrical engineering). The code is broken up into a handful of small functions. There is no need to make those functions available so I wrap them all in an outer function which is exported in the module. Like this:

Module
using StaticArrays
using FFTW
using LinearAlgebra

export csm

function csm(input1,input2,...,input10)

     function func1(input1)
          #stuff
     end

     function func2(input2,input3)
          #stuff
     end

     #...more functions...

     function func7(input10)
          #stuff
     end

return result1, result2, result3
end
end

Presently, the inputs to the outer function are a bit unwieldy

The 10 inputs consist of a couple of constants and arrays of varying length. Some of the arrays are arrays of coordinates defined using StaticArrays.jl (i.e. SVectors).

A function call looks like this:

uniformChargeDensity, nonuniformChargeDensity, chargeValues, zArray = 
csm(subconductorAxisCoordinates, subconductorSurfaceCoordinates, subconductorVoltages, baseChargeCoordinates, matchPointCoordinates, matchPointVoltages,
,arcChargeCoordinates, arcMatchCoordinates, N, γRange)

Calls to this function are iterative, but only two of the inputs change between iterations. An external function will initially read in a text file to populate the variables that do not change between iterations. Do you have a suggestion for the most Julian way to organize these inputs?

I did see this thread: Functions with many arguments, but wasn’t sure if that is the right solution for this iterative situation.

Definitely use a struct to organize your inputs. You can use the @unpack macro frun UnPack.jl to save typing.

Also I would say you should put the smaller functions outside of each function. Easier to make sure you don’t get scoping confused.

1 Like

Thanks. Can you elaborate on this statement a bit? Are you saying it would be best not to wrap the smaller functions in the overall function?

Just bugs like this, that’s all

julia> function outerfun()
       x = 1
       function innerfun(xi)
           x
       end
       
       innerfun(5)
       end
outerfun (generic function with 1 method)

julia> outerfun()
1
1 Like

Ok. So I had planned on having three functions defined in three packages:

input1,input2...input7 = readUserDataFromCSV(path)         
input8,input9,input10 = functionThatControlsIterations(output1,output2,output3)
output1,output2,output3 = csm(input1,input2...input10)

based on a quick read of struct and mutable struct it seems I could define:

struct staticInputs
     input1::Int64
     input2::Float64
     input3::Array{Float64,1}
     ...
     input7::Array{SArray{Tuple{3},Float64,1,3} #not sure how to specify type for SVector
end

mutable struct changingInputs
     input8::Array{Float64,1}
     input9::Array{SArray{Tuple{3},Float64,1,3}
     input10::Float64
end

mutable struct changingOutputs
     output1::Array{Float64,1}
     output2::Array{Float64,1}
     output3::Array{Float64,1}
end

and my function calls could become something like:

staticInputs = readUserDataFromCSV(path)         
changingInputs = functionThatControlsIterations(changingOutputs)
changingOutputs = csm(staticInputs, changingInputs)

Is that the right idea?

If these three functions are separate packages, do I need to have the applicable struct / mutable struct definitions in each? Or would it be better to just define one package that contains all three functions?

Parameters.jl may be helpful here.

1 Like

Maybe. It’s always hard to say exactly how someone should organize their data, but I think a good principle is that struct or mutable struct should refer to some group of data that have a coherent meaning in your application. You can then create structs that hold other structs to create a hierarchical organization of information. As a simple example, you might have a Point with x, y, and z fields and a Color with r, g, and b fields and then a Voxel consisting of a Point and a Color. In that way, Point, Color and Voxel are all structs which refer to coherent, meaningful pieces of information.

Array{SArray{Tuple{3},Float64,1,3} #not sure how to specify type for SVector

You can do Array{SVector{3, Float64}} as a shorthand for what you’ve written. You can verify this with:

julia> SVector{3, Float64} === SArray{Tuple{3},Float64,1,3}
true

where === checks for equality in the strictest possible sense.

Also note that you omitted the number of dimensions in your Array, so you would need:

Array{SVector{3, Float64}, 1}

to fully specify the type. As a more convenient shorthand, you can use Vector instead of Array{..., 1}:

julia> Vector{SVector{3, Float64}} === Array{SVector{3, Float64}, 1} === Array{SArray{Tuple{3}, Float64, 1, 3}, 1}
true

No. Your type definitions should appear in precisely one place. If you repeat your type definitions multiple times or include() the file that defines them multiple times in separate modules, then you will end up with multiple identical but incompatible type definitions with the same name. You definitely don’t want that.

I’m not sure exactly how you are planning on organizing your code, but I would suggest starting out with your struct and function definitions in a single module and only splitting things up as needed to keep things organized as you work on them.

3 Likes