A small "API" I made for ModelingToolkit/MoL, need feedback on the general API structure

Essentially the API is “functional”, where parameterized functions are defined which give the variables, parameters, equations…etc of the model. The goal is to have a nice easy way to do experiments that involve changing the model itself including parameter, eqs…etc based on a descriptor object.

It’s very simple and small, only consisting of this block:

struct Model
    variables::Any
    parameters::Any
    dep_vars::Any
    equations::Any
    bcs::Any
    domain::Any
    discretizer::Any
end

function (m::Model)(global_parameters, return_sys=false)
    variables = m.variables(global_parameters)
    params = m.parameters(global_parameters)
    dep_vars = m.dep_vars(global_parameters)
    equations = m.equations(global_parameters)
    bcs = m.bcs(global_parameters)
    domain = m.domain(global_parameters)
    discretizer = m.discretizer(global_parameters)

    default_params = [param => getdefault(param) for param in params]
    @named pdesys = PDESystem(equations, bcs, domain, variables, dep_vars, default_params)
    prob = discretizer(pdesys)
    if return_sys
        prob, pdesys
    else
        prob
    end
end

A small example below (using MoL), I used a float as the descriptor:

	using OrdinaryDiffEq
using ModelingToolkit
using MethodOfLines
using DomainSets
using Plots
using ModelDefiner

# Parameters, variables, and derivatives
@parameters t x
@variables u(..)
Dt = Differential(t)
Dxx = Differential(x)^2

function construct_eqs(i)
	function coeff(t, x)
		return i
	end
	return [Dt(u(t, x)) ~ coeff(t,x) * Dxx(u(t, x))]
end
function construct_bcs(i)
	[u(0, x) ~ cos(x),
	u(t, 0) ~ exp(-t),
	u(t, 1) ~ exp(-t) * cos(1)]
end
function construct_variables(i)
	[t, x]
end
function construct_domains(i)
	[t ∈ Interval(0.0, 1.0),
	x ∈ Interval(0.0, 1.0)]
end
function construct_depvars(i)
	return [u(t, x)]
end
function construct_discretizer(i)
	dx = 0.1
	order = 2
	discretization = MOLFiniteDifference([x => dx], t)
	
	# Convert the PDE problem into an ODE problem
	discretizer(pdesys) = discretize(pdesys,discretization)
	return discretizer
end
function construct_parameters(i)
	return []
end

m = Model(construct_variables, construct_parameters, construct_depvars, construct_eqs,
	construct_bcs, construct_domains, construct_discretizer)

prob = m(1.)
sol = solve(prob, Tsit5(), saveat=0.2)

# Plot results and compare with exact solution
discrete_x = sol[x]
discrete_t = sol[t]
solu = sol[u(t, x)]

plt = plot()

for i in eachindex(discrete_t)
	plot!(discrete_x, solu[i, :], label="Numerical, t=$(discrete_t[i])")
end
plt

I admit I do not see or understand the usefulness of it. It is wrapping basic code in a type and function. Why not just call the code directly…?

I wanted a way to programmatically change a large PDE system. I only write the core equations, then create a struct with which I can define several systems by changing the core system; I may change the equations and thus have to add new parameters for example.

I understand that, but I hardly see any “API”, much less the need for a package for that. As far as I can tell you just need the function

function some_name(i, return_sys=false)

as all of your constructors depend only on i (which itself is already extremely specific).

Thought of it more as a “wrapper” than an API.

function some_name(i, return_sys=false)

That’s what I’m doing (kinda) at the moment. Thought I’d make it general. I agree a package is not needed for this. I’m working on something else where I need the parameters explicitly written, so I just wrote this quickly as a wrapper.

Though you’re confirming my suspicion that this is “overdone”.