BAMresearch / bayem

Implementation and derivation of "Variational Bayesian inference for a nonlinear forward model." [Chappell et al. 2008] for arbitrary, user-defined model errors.
MIT License
2 stars 1 forks source link

WIP: Vector parameters #10

Closed TTitscher closed 3 years ago

TTitscher commented 3 years ago

Solving #8 I basically added a member latent._index_length that keeps track of the length of the parameters.

a -> 1 v -> [1, 2, 3, 4, 5]

latent.add("a" ...) latent.add("v" ...)

latent._index_length --> [1, 5]

In latent.update(numbers) the numbers (in this case of required length 6) are split into two vectors (for two parameters) of length 1 and 5 and assigned accordingly.

Could we live with something like that?

[Still WIP as following things like defining priors for those are not yet considered.]

joergfunger commented 3 years ago

That looks already very good. I think it would be nice to have an additional test case that illustrates and tests this vector parameter in a joint list (so that this vector valued parameter is a latent parameter in a joint parameter list).

TTitscher commented 3 years ago

Thanks for your feedback, almost everything good and "solvable" IMO.

And I am sorry for accidentally mixing up the branches, but I'll now leave it here because of our discussion.

@joergfunger If you follow the existing structure of the main file, how would you like to include information on how to correlate what? I mean the maths is (for easy cases) clear, but what would be a convenient/reasonable/readable way to tell the library what to do?

joergfunger commented 3 years ago

Maybe we should step back and try to think how the user would implement his/her problem.

  1. The user defines a (set of) forward models (e.g. compression test, tensile test) that has as input a set of parameters, and as output something related to each individual sensor. In the current approach, this is a scalar/vector. Maybe this should already include spatial/temporal information, so that we that each sensoroutput is e.g. of type Vector, TimeDependentVector, SpaceDependentVector or SpaceTimeDependentVector (this should cover everything). The forward model also provides a method that creates a fw_prms (initialized with Nones for values supposed to be added by the inference)

  2. The user defines a model error that reads in (either from a file or from the database..) and internally stores the data as well as modifies the fw_prms in order to include metadata (e.g. in the metadata of the compression test this could be the information of the measured environmental temperature, the size of the specimen, the time steps with data). Note that it's the users choice if he/she adds the information directly to the constructor (e.g. in case of a mesh this seems a reasonable choice) or if it's added as a parameter that can be changed. This choice is primarily influenced by the fact, whether we could potentially think about making these parameters latent. From a personal point of view, I think it should be easy to move parameters from constructor to the fw_prms and vice versa, I would prefer a dict type of approach for both. (and it simplifies the documentation since both dicts can just be serialized).

  3. The user defines an InferenceProblem. Then a loop over all experiments to be added is performed e.g.

    inference_problem = InferenceProblem() 
    inference_problem.add_inference_prm('E', someDistributionWithParameters) #note that the distribution parameters given can refer to other parameters previously defined here 
    inference_problem.add_inference_prm('sigma_f', 0.1) #deterministic 
    inference_problem.add_inference_prm('l_corr', 2.) #deterministic 
    for test in tensile_tests_yaml_files:  #or whatever is the loop that provides the data
    # define model error
    ################
    model_error = tensile_test_model_error(my_fw_tensile_model, ..)
    fw_prm = model_error.get_parameter()
    key_this_tensile_test = inference_problem.add_me(model_error, fw_prm)
    
    # define fw_prm and map to inference_prm 
    ################
    # this is for shared variables - we need to map the inference prm to the fw model parameters 
    inference_problem.map_inference2fw_prm('E', key_this_tensile_test, 'Youngs modulus')
    # for variables that are individual for each experiment, we can simultaneously add them to the global list 
    # inference_prm with a given key are automatically local
    inference_problem.add_inference_prm('offset', key_this_tensile_test, 'initialOffset')
    
    # define groups of model errors that each contribute similarly to the loglike i.e. noise groups 
    inference_problem.loglike.add_me(key_model_error, 'T', 'sigma_T') # just uncorrelated sensors
    inference_problem.loglike.add_me_correlated_in_time(key_this_tensile_test, 'force_sensor', 'sigma_f', kernelfunction('l_corr'))    

    In the final application we then implement a loglike function that just loops over the loglike.me and sums up the individual contributions. If you have another bending test, just add this directly after the loop over the tensile tests.

TTitscher commented 3 years ago

In short, I would agree with the steps. I would, however, treat the noise models similar to the model errors. 1) The user creates it. 2) The user adds it to the inference model which assigns both the noise model and the noise parameter list to a noise key. 3) The noise parameter list is treated like the model error parameter list, so the user sets parameters, sets parameters latent, shares them, defines priors... 4) The treatment/processing of the information stays algorithm dependent.