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

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

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
	return [Dt(u(t, x)) ~ coeff(t,x) * Dxx(u(t, x))]
function construct_bcs(i)
	[u(0, x) ~ cos(x),
	u(t, 0) ~ exp(-t),
	u(t, 1) ~ exp(-t) * cos(1)]
function construct_variables(i)
	[t, x]
function construct_domains(i)
	[t ∈ Interval(0.0, 1.0),
	x ∈ Interval(0.0, 1.0)]
function construct_depvars(i)
	return [u(t, x)]
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
function construct_parameters(i)
	return []

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])")

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”.