Coverage for physioblocks / configuration / description / blocks.py: 84%

92 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-09 16:40 +0100

1# SPDX-FileCopyrightText: Copyright INRIA 

2# 

3# SPDX-License-Identifier: LGPL-3.0-only 

4# 

5# Copyright INRIA 

6# 

7# This file is part of PhysioBlocks, a library mostly developed by the 

8# [Ananke project-team](https://team.inria.fr/ananke) at INRIA. 

9# 

10# Authors: 

11# - Colin Drieu 

12# - Dominique Chapelle 

13# - François Kimmig 

14# - Philippe Moireau 

15# 

16# PhysioBlocks is free software: you can redistribute it and/or modify it under the 

17# terms of the GNU Lesser General Public License as published by the Free Software 

18# Foundation, version 3 of the License. 

19# 

20# PhysioBlocks is distributed in the hope that it will be useful, but WITHOUT ANY 

21# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 

22# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. 

23# 

24# You should have received a copy of the GNU Lesser General Public License along with 

25# PhysioBlocks. If not, see <https://www.gnu.org/licenses/>. 

26 

27""" 

28Define how to configure block. 

29""" 

30 

31from typing import Any 

32 

33from physioblocks.configuration.base import Configuration, ConfigurationError 

34from physioblocks.configuration.constants import ( 

35 BLOCK_FLUX_TYPE_ITEM_ID, 

36 MODEL_COMPONENT_TYPE_ITEM_ID, 

37 SUBMODEL_ITEM_ID, 

38) 

39from physioblocks.configuration.functions import load, save 

40from physioblocks.description.blocks import BlockDescription, ModelComponentDescription 

41from physioblocks.registers.load_function_register import loads 

42from physioblocks.registers.save_function_register import saves 

43from physioblocks.registers.type_register import ( 

44 get_registered_type, 

45 get_registered_type_id, 

46) 

47from physioblocks.simulation.time_manager import TIME_QUANTITY_ID 

48 

49VARIABLES_ITEM_ID = "variables" 

50"""Constant for variables item id in the global id dict""" 

51 

52PARAMETERS_ITEM_ID = "parameters" 

53"""Constant for parameters item id in the global id dict""" 

54 

55SAVED_QUANTITIES_ITEM_ID = "saved_quantities" 

56"""Constant for saved quantities item id in the global id dict""" 

57 

58 

59@saves(ModelComponentDescription) 

60def save_model_component_config( 

61 model_component: ModelComponentDescription, *args: Any, **kwargs: Any 

62) -> Configuration: 

63 """ 

64 Create a Configuration for a model description. 

65 

66 :param model_component: the model_component description 

67 :type model_component: ModelComponentDescription 

68 

69 :return: the configuration 

70 :rtype: Configuration 

71 """ 

72 

73 description_type_id = get_registered_type_id(type(model_component)) 

74 model_type_id = get_registered_type_id(model_component.described_type) 

75 config = Configuration(description_type_id) 

76 config[MODEL_COMPONENT_TYPE_ITEM_ID] = model_type_id 

77 

78 # save local ids 

79 global_ids = _get_global_ids(model_component) 

80 

81 config.configuration_items.update(global_ids) 

82 

83 # save sub-models 

84 if len(model_component.submodels) > 0: 

85 config[SUBMODEL_ITEM_ID] = save(model_component.submodels, *args, **kwargs) 

86 

87 return config 

88 

89 

90@saves(BlockDescription) 

91def save_block_description_config( 

92 block_description: BlockDescription, *args: Any, **kwargs: Any 

93) -> Configuration: 

94 config: Configuration = save_model_component_config(block_description) 

95 config[BLOCK_FLUX_TYPE_ITEM_ID] = block_description.flux_type 

96 return config 

97 

98 

99def _get_global_ids(model: ModelComponentDescription) -> dict[str, Any]: 

100 global_ids: dict[str, Any] = {} 

101 

102 for local_id, global_id in model.global_ids.items(): 

103 if global_id == TIME_QUANTITY_ID: 

104 global_ids[TIME_QUANTITY_ID] = TIME_QUANTITY_ID 

105 elif model.described_type.has_internal_variable(local_id): 

106 if VARIABLES_ITEM_ID not in global_ids: 

107 global_ids[VARIABLES_ITEM_ID] = {} 

108 global_ids[VARIABLES_ITEM_ID][local_id] = global_id 

109 elif model.described_type.has_saved_quantity(local_id): 

110 if SAVED_QUANTITIES_ITEM_ID not in global_ids: 

111 global_ids[SAVED_QUANTITIES_ITEM_ID] = {} 

112 global_ids[SAVED_QUANTITIES_ITEM_ID][local_id] = global_id 

113 else: 

114 if PARAMETERS_ITEM_ID not in global_ids: 

115 global_ids[PARAMETERS_ITEM_ID] = {} 

116 global_ids[PARAMETERS_ITEM_ID][local_id] = global_id 

117 

118 return global_ids 

119 

120 

121@loads(ModelComponentDescription) 

122def load_model_component_config( 

123 configuration: Configuration, 

124 configuration_key: str, 

125 configuration_type: type[ModelComponentDescription], 

126 configuration_object: BlockDescription | None = None, 

127 additional_model_arguments: dict[str, Any] | None = None, 

128 *args: Any, 

129 **kwargs: Any, 

130) -> ModelComponentDescription: 

131 """ 

132 Load a model component description from a configuration. 

133 

134 :param config: the configuration 

135 :type config: Configuration 

136 

137 :return: the model description 

138 :rtype: ModelComponentDescription 

139 """ 

140 additional_model_arguments = ( 

141 {} if additional_model_arguments is None else additional_model_arguments 

142 ) 

143 model_type = get_registered_type(configuration[MODEL_COMPONENT_TYPE_ITEM_ID]) 

144 

145 model_ids = {} 

146 model_ids = _get_model_global_ids(configuration) 

147 

148 sub_models_desc = {} 

149 if SUBMODEL_ITEM_ID in configuration.configuration_items: 

150 sub_models_desc = load(configuration[SUBMODEL_ITEM_ID], *args, **kwargs) 

151 

152 if configuration_object is not None: 

153 new_model_ids = configuration_object.global_ids.copy() 

154 new_model_ids.update(model_ids) 

155 new_submodels = configuration_object.submodels 

156 new_submodels.update(sub_models_desc) 

157 else: 

158 new_model_ids = model_ids 

159 new_submodels = sub_models_desc 

160 

161 return configuration_type( 

162 configuration_key, 

163 model_type, 

164 global_ids=new_model_ids, 

165 submodels=new_submodels, 

166 **additional_model_arguments, 

167 ) 

168 

169 

170@loads(BlockDescription) 

171def load_block_description_config( 

172 configuration: Configuration, 

173 configuration_key: str, 

174 configuration_type: type[ModelComponentDescription], 

175 configuration_object: BlockDescription | None = None, 

176 *args: Any, 

177 **kwargs: Any, 

178) -> BlockDescription: 

179 if BLOCK_FLUX_TYPE_ITEM_ID not in configuration: 

180 raise ConfigurationError( 

181 str.format( 

182 """Missing item {0} in block {1}.""", 

183 BLOCK_FLUX_TYPE_ITEM_ID, 

184 configuration_key, 

185 ) 

186 ) 

187 block_additional_arguments = { 

188 BLOCK_FLUX_TYPE_ITEM_ID: configuration[BLOCK_FLUX_TYPE_ITEM_ID] 

189 } 

190 return load_model_component_config( 

191 configuration, 

192 configuration_key, 

193 configuration_type, 

194 configuration_object, 

195 *args, 

196 additional_model_arguments=block_additional_arguments, 

197 **kwargs, 

198 ) # type: ignore 

199 

200 

201def _get_model_global_ids(config: Configuration) -> dict[str, str]: 

202 model_ids = config.configuration_items.copy() 

203 

204 # remove the submodels and model type keys from the model ids (keep them in the 

205 # configuration) 

206 if SUBMODEL_ITEM_ID in model_ids: 

207 model_ids.pop(SUBMODEL_ITEM_ID) 

208 

209 if MODEL_COMPONENT_TYPE_ITEM_ID in model_ids: 

210 model_ids.pop(MODEL_COMPONENT_TYPE_ITEM_ID) 

211 

212 if BLOCK_FLUX_TYPE_ITEM_ID in model_ids: 

213 model_ids.pop(BLOCK_FLUX_TYPE_ITEM_ID) 

214 

215 # Recursivlely unfold global id in configuration or dict: 

216 model_ids = _unfold_global_ids(model_ids) 

217 

218 return model_ids 

219 

220 

221def _unfold_global_ids(global_ids: dict[str, Any]) -> dict[str, str]: 

222 result = {} 

223 

224 for key, value in global_ids.items(): 

225 if isinstance(value, str): 

226 result[key] = value 

227 elif isinstance(value, dict): 

228 result.update(_unfold_global_ids(value)) 

229 elif isinstance(value, Configuration): 

230 result.update(_unfold_global_ids(value.configuration_items)) 

231 else: 

232 raise ConfigurationError( 

233 str.format( 

234 """{0}: {1} is not configurable as a model local to global id.""", 

235 key, 

236 value, 

237 ) 

238 ) 

239 return result