Implement two variants of a simulation which works with the same objects

I want to simulate two variants of a process that involves the same objects (e.g. a stochastic process on the unit interval [0,1) or on the circle \mathbb R/\mathbb Z).
For the first variant, I’ve grouped the objects defining the process in a struct System and defined a function simulate(s::System) that performs the simulation.
For the second variant, I only need to change few things in simulate. Some possibilities on how to implement the second variant are the following ones:

  1. Modify System so that it contains an additional field, e.g. an Enum Variant, which is then used by simulate to determine which variant to run;
  2. Define another function simulate2;
  3. Define a different struct System2 and use multiple dispatch on simulate.

Q1. Any advice on what would be the most julionic way to do that?

I feel option 3 would be the most appropriate one. However, in that case I’d need two structs System2 and System1 which have the same fields, and differs only by the name of their type. Here some possibilities are the following ones:

  • 3.1. Just repeat code.
  • 3.2. Use code generation:
for type in (:System1, :System2)
   eval(quote
	   struct $type
		   field1
		   ...
	   end 
   end)
end
  • 3.3. Create a parametric type System{T} and abstract types Variant1 and Variant2, and then declare the system with System{Variant1}(...) etc.

Q2. Any advice on what would be the most julionic way to do that?

I feel that option 3.3 could be a misuse of the concept of parametric type, since the parametric type is not used in any field; on the other hand, if one wants to have the possibility to multiple dispatch on structs holding the same objects, it looks like a nice way to make the situation transparent.

1 Like

Conceptually, the information which simulation routine to run, is independent of the system itself. Therefore it feels clunky to attach that information to the System type in any way. This rules out options 1 and 3 for me.

Personally, I prefer a version of option 2. Define functions _simulate_variant_i(::System) and a higher-level function to dispatch on the correct one

function simulate(s::System; variant)
  if variant==1
    _simulate_variant_1(s)
  else
  [...]
  end
end 

Another option – if you insist on multiple dispatch – is to wrap a System in another structure, e.g.

struct SimVariant1
   s::System
end

and dispatch on the wrapper.

Of course this can be made parametric, and you are free to attach the simulation result to the wrapper if that is opportune, i.e.

struct Simulation{Variant}
    s::System
    solution
end

simulate!(s::Simulation{Variant1}) = ...
simulate!(s::Simulation{Variant2}) = ...

If the struct is the exact same thing, I would use 2 to make the intent clear.