|
33 | 33 |
|
34 | 34 |
|
35 | 35 | class SustainableConcreteModel: |
| 36 | + """Multi-output model that jointly predicts GWP and compressive strength. |
| 37 | +
|
| 38 | + The model consists of a GWP model (independent of curing time) and a strength |
| 39 | + model (dependent on composition *and* time). At optimisation time the strength |
| 40 | + model is sliced at each of the ``strength_days`` via ``FixedFeatureModel`` to |
| 41 | + produce a ``ModelList`` that maps composition only to ``[GWP, 1-day strength, |
| 42 | + 28-day strength, ...]``. |
| 43 | + """ |
| 44 | + |
36 | 45 | def __init__( |
37 | 46 | self, |
38 | 47 | strength_days: list[int], |
@@ -100,37 +109,82 @@ def _set_d(self, d: int) -> None: |
100 | 109 | if self.d is None: |
101 | 110 | self.d = d |
102 | 111 |
|
103 | | - def get_model_list(self) -> ModelList: |
104 | | - """Returns a ModelList modeling the GWP and compressive strength objectives as a function |
105 | | - of composition only. |
106 | | - Converts the strength and gwp models into a model list of independent models for gwp, |
107 | | - and x-day strengths, by fixing the time input of the strength model at 1 and 28 days. |
| 112 | + def get_model_list( |
| 113 | + self, fixed_features: dict[int, float] | None = None |
| 114 | + ) -> ModelList: |
| 115 | + """Returns a ``ModelList`` modelling GWP and compressive strength as a |
| 116 | + function of composition only. |
| 117 | +
|
| 118 | + Converts the strength and GWP models into a model list of independent |
| 119 | + models for GWP and *x*-day strengths by fixing the time input of the |
| 120 | + strength model at each ``strength_day``. |
| 121 | +
|
| 122 | + Args: |
| 123 | + fixed_features: Optional mapping from input column **index** to a |
| 124 | + fixed value. When provided these features are fixed *in |
| 125 | + addition to* the Time dimension for the strength models, and |
| 126 | + the non-Time entries are also applied to the GWP model via |
| 127 | + ``FixedFeatureModel``. Useful for fixing e.g. |
| 128 | + ``Coarse Aggregates = 0`` in mortar mode. |
| 129 | +
|
| 130 | + Returns: |
| 131 | + A ``ModelList`` with ``1 + len(strength_days)`` sub-models. |
| 132 | +
|
| 133 | + Raises: |
| 134 | + ValueError: If the model has not been fitted yet. |
108 | 135 | """ |
109 | 136 | if self.d is None: |
110 | 137 | raise ValueError("Model not fit yet.") |
111 | | - models = [ |
112 | | - self.gwp_model, |
113 | | - *( |
| 138 | + |
| 139 | + time_idx = self.d - 1 # last column is Time |
| 140 | + |
| 141 | + if fixed_features is None: |
| 142 | + gwp_model = self.gwp_model |
| 143 | + else: |
| 144 | + non_time = {k: v for k, v in fixed_features.items() if k != time_idx} |
| 145 | + if non_time: |
| 146 | + gwp_indices = sorted(non_time.keys()) |
| 147 | + gwp_values = [non_time[i] for i in gwp_indices] |
| 148 | + gwp_model = FixedFeatureModel( |
| 149 | + base_model=self.gwp_model, |
| 150 | + dim=self.d - 1, # GWP input has no Time |
| 151 | + indices=gwp_indices, |
| 152 | + values=gwp_values, |
| 153 | + ) |
| 154 | + else: |
| 155 | + gwp_model = self.gwp_model |
| 156 | + |
| 157 | + strength_models = [] |
| 158 | + for day in self.strength_days: |
| 159 | + indices = [time_idx] |
| 160 | + values: list[float] = [float(day)] |
| 161 | + if fixed_features is not None: |
| 162 | + for idx, val in sorted(fixed_features.items()): |
| 163 | + if idx != time_idx: |
| 164 | + indices.append(idx) |
| 165 | + values.append(val) |
| 166 | + strength_models.append( |
114 | 167 | FixedFeatureModel( |
115 | 168 | base_model=self.strength_model, |
116 | 169 | dim=self.d, |
117 | | - indices=[self.d - 1], |
118 | | - values=[day], |
| 170 | + indices=indices, |
| 171 | + values=values, |
119 | 172 | ) |
120 | | - for day in self.strength_days |
121 | | - ), |
122 | | - ] |
123 | | - model = ModelList(*models) |
124 | | - return model # for use with multi-objective optimization |
| 173 | + ) |
| 174 | + |
| 175 | + return ModelList(gwp_model, *strength_models) |
125 | 176 |
|
126 | 177 | # TODO: add plot_strength_curve utility for visualizing predicted strength |
127 | 178 | # curves as a function of time for a given composition. |
128 | 179 |
|
129 | 180 |
|
130 | 181 | class FixedFeatureModel(Model): |
131 | | - # advantage: only need to implement posterior for it to work with qNEHI |
132 | | - # disadvantage: makes the strength outputs independent (IDEA: could add joint model) |
133 | | - # TODO: check that these are appended before the InputTransforms are applied, not after. |
| 182 | + """Wraps a GP model to fix a subset of inputs to constant values. |
| 183 | +
|
| 184 | + At evaluation time the fixed features are spliced back into the input |
| 185 | + tensor before delegating to the ``base_model``. |
| 186 | + """ |
| 187 | + |
134 | 188 | def __init__( |
135 | 189 | self, |
136 | 190 | base_model: Model, |
@@ -202,9 +256,19 @@ def posterior(self, X: Tensor, *args, **kwargs) -> Posterior: |
202 | 256 |
|
203 | 257 | @property |
204 | 258 | def num_outputs(self) -> int: |
205 | | - return self.base_model.num_outputs # need to adjust if we batch fixed features |
| 259 | + """The number of outputs of the base model.""" |
| 260 | + return self.base_model.num_outputs |
206 | 261 |
|
207 | 262 | def subset_output(self, idcs: list[int]) -> FixedFeatureModel: |
| 263 | + """Returns a new ``FixedFeatureModel`` whose base model is subset to |
| 264 | + the given output indices. |
| 265 | +
|
| 266 | + Args: |
| 267 | + idcs: Output indices to keep. |
| 268 | +
|
| 269 | + Returns: |
| 270 | + A ``FixedFeatureModel`` wrapping the subset base model. |
| 271 | + """ |
208 | 272 | return FixedFeatureModel( |
209 | 273 | base_model=self.base_model.subset_output(idcs), |
210 | 274 | dim=self._dim, |
|
0 commit comments