Conversation
Extract qt in output%response%q if zeros routing module
…xternal routing model such as Gamma. Add a second rule for the differentiation of Smash: output%response%qt / parametersDT
Simplify _constant.py to handle these names correctly
For unknown reason 2 variable has some slight modification: - custom_bayesian_optimize.zero-gr4-lr.custom_set_1.sim_q : mean_diff = 0.00424 - optimize.zero-gr6-lr.distributed.sim_q : mean_diff = 0.00404
inoelloc
left a comment
There was a problem hiding this comment.
Voila une première passe. Je pense qu'on peut rediscuter de l'implementation pour récupérer les gradients par rapport aux débits pour etre plus flexible. De cette question de options_b et options_d a nettoyer et le truc de créer une nouvelle subroutine à différencier pour eviter le b0 et d0.
Il doit y avoir un truc avec ta version de ruff ou je ne sais quoi mais j'ai l'impression qu'on a pas la meme version pour faire le formattage et le check. Essaye peut etre un pip install --upgrade ruff avant de refaire un make format et check
| # ~ MODULE_RR_PARAMETERS = dict( | ||
| # ~ **SNOW_MODULE_RR_PARAMETERS, | ||
| # ~ **HYDROLOGICAL_MODULE_RR_PARAMETERS, | ||
| # ~ **ROUTING_MODULE_RR_PARAMETERS, | ||
| # ~ ) |
There was a problem hiding this comment.
Super merci,
j'ai fais un un upgrade de ruff, mais ca n'a rien changé ...
| !~ When conditionning this allocatation, tapenade force | ||
| !~ its value to zeros before calling SIMULATION_B... | ||
| if (setup%routing_module == "zero") then | ||
| allocate (this%qt(mesh%nac, setup%ntime_step)) | ||
| this%qt = -99._sp | ||
| else | ||
| !save memory | ||
| allocate (this%qt(1, 1)) | ||
| this%qt = -99._sp | ||
| end if |
There was a problem hiding this comment.
Est ce que finalement ca serait pas plus simple d'avoir une variable qui s'appelle q_domain. Comme ca, on pourrait elargir le code en disant qu'on peut récupérer les gradients du débit routé de smash et avec le routage zero, on retombe sur le debit elementaire ?
There was a problem hiding this comment.
Et est ce que ca serait pas possible d'allouer par défaut à (1, 1) quand on initialise le modèle. Puis quand on lance un run si on demande le gradient par rapport aux debits, on realloue plutot que routing zero ? Désolé, j'essaye de voir jusqu'ou on peut aller pour etendre ce couplage smash par rapport à la dernière reu a Aix.
There was a problem hiding this comment.
Alors oui, on peut renommer la variable en qdomain, mais je trouve que ca porte à confusion avec qdomain du type derivé mwd_return.f90. En plus cette variable sera visibles par les utilisateurs. Sinon on l'appelle qac (Q Active Cells) ?
A la place d'allouer en fonction du module de routage zeros, on ajoute une ou deux option dans setup:
return_grad_qe = Bool
return_grad_q = Bool
ou une seule variable:
return_opt_grad = "zero" | qe" | "q" (default="zero") Ca donne quoi None quand ca passe en fortran ?
On alloue qac en fonction des variables précédente:
allocate (this%qac(mesh%nac, setup%ntime_step))
ensuite dans mw_forward on test (ajuster les tests en fonction du choix de setup):
if (setup%return_grad_qe == .true.) then
do i = 1, mesh%nac
output%response%qac(i, time_step) = checkpoint_variable%ac_qtz(i, setup%nqz)
end do
end if
if (setup%return_grad_q == .true.) then
do i = 1, mesh%nac
output%response%qac(i, time_step) = checkpoint_variable%ac_qz(i, setup%nqz)
end do
end if
ll faut aussi vérifier dans Python dans model/_standardize.py que soit return_grad_q ou return_grad_qe soit True et pas les deux. Donc peut être plus simple de mettre une seule variable dans setup: return_opt_grad
Qu'est ce que tu penses de cette option ?
There was a problem hiding this comment.
J'ai opté pour la solution avec dans setup la nouvelle variable: return_opt_grad =["none" | "qe" | "q"] !
C'est pas mal, tu me dis...
du coup on peut avoir les grads des param par rapport aux débits élémentaires ou aux débits sur le domaine.
| type(OptionsDT) :: option_d | ||
| option_d = options |
There was a problem hiding this comment.
Je viens de me rendre compte que ca remonte à un commit de 2023, l'ajout de options_d et options_b aux routines générées par Tapenade .. Je ne sais pas du tout pourquoi ca ne plante pas .. Mais mieux vaut tard que jamais. Par contre, l'idée de la fonction dans mw_forward.f90 et de juste wrapper à l'identique la subroutine tapenade. Je prefererais que options_d et options_b soient passés en argument de cette fonction. On modifiera dans Python pour ajouter une copy d'options dans wrap_forward_run
There was a problem hiding this comment.
Oui ok, mais j'ai eu un pb avec la copy de ces types dérivés en Python je crois... C'est pour cela que j'avais choisi cette solution. Je vais retenter.
| c_sources, f90_sources, f90wrap_f90_sources, f2py_f90wrap_sources, | ||
| c_args: ['-O3', c_ignore_warnings], | ||
| fortran_args: ['-O3', '-march=native', '-cpp', fortran_ignore_warnings], | ||
| #fortran_args: ['-Wall', '-Wextra', '-fmax-errors=1', '-cpp', '-g', '-fcheck=all', '-fbacktrace', fortran_ignore_warnings], |
There was a problem hiding this comment.
Supprimer
| #fortran_args: ['-Wall', '-Wextra', '-fmax-errors=1', '-cpp', '-g', '-fcheck=all', '-fbacktrace', fortran_ignore_warnings], |
| "-head", | ||
| r"base_forward_run(parameters.control.x)\(output.response.qt)", |
There was a problem hiding this comment.
Je me demande si ca serait pas plus simple de créer une nouvelle routine, je sais pas : base_forward_run_q et on met la regle de diff sur cette subroutine. Ca permettra d'tre plus clair, de pas avoir un base_forward_run_b0 et ca sera exactement le base_forward_run mais sans le calcul de la fonction cout.
There was a problem hiding this comment.
Oui, bonne idée !
J'ai fait comme tu as proposé. Ca fonctionne bien.
- New diff rule for q (qdomain) and qe (elemntary discharge) - rename response.qt -> response.qac - New forward routine to handle new diff rules
|
Salut François, Toujours un problème de make check/format... j'ai pourtant fais la mise )à jour des paquets. ou souhaites tu que l'on documente cette option pour renvoyer les nouveaux gradients dans la doc ? A++ |
|
|
||
| end do | ||
|
|
||
| if (setup%return_opt_grad == "qe") then |
There was a problem hiding this comment.
Pourquoi nommer qe et pas qt ?
There was a problem hiding this comment.
Pour Q élémentaire (1 pixel). On peut changer de nom si il faut.
Peut-être on peu trouver un meilleur terme d'ailleurs ?
|
Je suis pas sur de bien comprendre comment on récupère les gradients au final. Quelle fonction de l'API renvoie les gradients avec cette option ? |
|
Salut, Les gradients renvoyés sont aux choix:
L'option qui permet de contrôler cela s'appelle : setup%return_opt_grad L'API Fortran renvoie les gradients via les fonctions suivantes:
L'API Python est la fonction :
Pour avoir les gradient scoté Python j'utilise la fonction C'est là ou un utilisateur de Smash ne peut rien faire car l'appel de cette dernière est complexe. Je me suis inspiré des fonctions dans core/simulation/optimize/optimize.py pour en déduire la fonction suivante utilisé dans Gamma. Je pense que ce serait bien de l'intégrer dans l'API Python pour que les utilisateur puisse utiliser ce gradient. De même pour le gradient de base Cost/parameters ? Voici la fonction python: def _get_smash_gradient(model, control_smash):
from smash.fcore._mwd_options import OptionsDT
from smash.fcore._mwd_returns import ReturnsDT
from smash.core.model._build_model import _map_dict_to_fortran_derived_type
from smash.core.simulation.optimize._standardize import _standardize_optimize_args
from smash.fcore._mwd_parameters_manipulation import (
parameters_to_control as wrap_parameters_to_control,
)
from smash.core.simulation.optimize._tools import (
_handle_bayesian_optimize_control_prior,
)
(
mapping,
optimizer,
optimize_options,
cost_options,
common_options,
return_options,
callback,
) = _standardize_optimize_args(
model,
mapping="distributed",
optimizer="lbfgsb",
optimize_options={
"parameters": control_smash["ParamList"],
"bounds": control_smash["bounds"],
},
cost_options=None,
common_options=None,
return_options=None,
callback=None,
)
wrap_options = OptionsDT(
model.setup,
model.mesh,
cost_options["njoc"],
cost_options["njrc"],
)
wrap_returns = ReturnsDT(
model.setup,
model.mesh,
return_options["nmts"],
return_options["fkeys"],
)
# % Map optimize_options dict to derived type
_map_dict_to_fortran_derived_type(optimize_options, wrap_options.optimize)
# % Map cost_options dict to derived type
_map_dict_to_fortran_derived_type(
cost_options, wrap_options.cost, skip=["control_prior"]
)
# % Map common_options dict to derived type
_map_dict_to_fortran_derived_type(common_options, wrap_options.comm)
# % Map return_options dict to derived type
_map_dict_to_fortran_derived_type(return_options, wrap_returns)
parameters = model._parameters.copy()
wrap_parameters_to_control(
model.setup,
model.mesh,
model._input_data,
parameters,
wrap_options,
)
parameters_q_b = smash.core.simulation.optimize._tools._get_parameters_q_b(
model, parameters, wrap_options, wrap_returns
)
grad = parameters_q_b.control.x.copy()
return grad |
|
Ok ok je vois. Ca me semble bizarre d'ajouter un argument au setup qu'un utilisateur ne peut pas utiliser. Il nous faut la fonction API publique pour recuperer les gradients. Sinon, on reste au niveau du dev. On pourrait avoir quelque chose comme ca : model.backward_run(mapping='uniform')
cost = model.output.cost
grad = model.output.gradOn garde tjs les tableaux dans # Grad Cost (par defaut)
model.backward_run(mapping='uniform')
# Grad Q
model.backward_run(mapping='uniform', grad_kind="q")Si |
…rtran are bad in return options
…gradient dJ/dX, dQ/dX, dQt/dx Clean code
|
Salut, J'ai créé une nouvelle fonction dans la class model, qui s'appelle backward_run. Cette fonction prend 2 arguments:
Lorsque que l'on appelle backward_run(), l'objet returns_options est complété en fonction de la valeur de grad_mode.
J'ai nettoyé le code (setup et constant.py) des anciennes variables (cf commit plus haut). j'ai ajouté de la doc dans core/simulation/_doc.py. Il faudrait faire un script pour tester et valider ces gradients... Voici un script pour tester: import smash mapping = "uniform" setup_cance, mesh_cance = smash.factory.load_dataset("Cance") Avec routagemapping = "distributed" setup_cance, mesh_cance = smash.factory.load_dataset("Cance") smash_model = smash.Model(setup_cance, mesh_cance) grad2, name2 = smash_model.backward_run(mapping=mapping, optimizer="lbfgsb", grad_mode="q") La normalisation des param dépend du type d'optimiseur, cela change les gradientsgrad3, name3 = smash_model.backward_run(mapping="uniform", optimizer="sbs", grad_mode="qt") sans routagesetup_cance["routing_module"] = "zero" # grad4 et grad5 sont identiques (pas de routage, q=qt) |
…peu pas récupérer les débits qt sur la grille ... Pour l'instant c'est fait dans la class model diretement dans forward_run... Suppression de option_b/_d dans base_forward_run_d et _b
|
Un dernier commit, car je me suis aperçu qu'il fallait aussi allouer response%qac dans forward_run afin de pouvoir récupérer les débits qt sur la grille. Pour l'instant j'ai mis ca direct dans la class model -> forward_run. Il faudra peut-être le déplacer dans les standardize ? Merci |
…ith previous version of smash. Get qt in the same way than q, i.e with q_domain flag. Add flag to get the gradient for q and qt vs X, and allocate output%response%qac
…her q_domain flags
- New input_derivatives parameters to pass the gradients of an external model to the adjoint of Smash: the full gradient of the model chain in therefore obtained : dj/dX=dj/dq*dq/dx. This fix issue for calibrating smash when coupled with the gamma routing model
|
Finalisation du travail:
|
…inal model comes with an allocated model.response.qac array. We need to allocate it when reading the hdf5.
|
Done by PR #541 |
Hello,
Here is a PR to build an interface with external routing model.
One can now define a routing_module as "zero" like the snow_module.
When this routing_module is set to zero, a new variable in response%qt is allocated to (mesh%nac, setup%ntime_step) (otherwise it is allocated to (1,1) and set to -99.). Then Smash will return the elementary discharge at every active cells in response%qt.
response%qt. may be used as input of any routing model.
Moreover, a new differentiation rule has been added: base_forward_run(parameters.control.x)(output.response.qt)
This produce the gradient of qt compare to the control vector. This gradient can be computed using
forward_run_b0(mw_forward.f90) and bound to an other gradients from the external routing model (with differentiation rule like routing_forward_run(cost)(output.response.qt)) <=> routing_forward_run(cost)(input_qt)) ).The baseline has been regenerated.
Everything are ok except 2 tests, but the diff are very small.
For unknown reason 2 variables has some slight modification:
- custom_bayesian_optimize.zero-gr4-lr.custom_set_1.sim_q : mean_diff = 0.00424
- optimize.zero-gr6-lr.distributed.sim_q : mean_diff = 0.00404
May be we need to document that also.
Thanks