@@ -929,31 +929,26 @@ def get_subset_sum_tensors(
929929# by `fit_gwp_model`.
930930# Derived via per-class least-squares regression on training data (which
931931# stores -GWP). Magnitudes here are the absolute emission factors.
932- DEFAULT_GWP_COEFFICIENTS : dict [ int , dict [ str , tuple [ float , float ]]] = {
932+ DEFAULT_GWP_COEFFICIENTS = {
933933 0 : { # Material Source 0
934- "Cement (kg/m3)" : (0.762613 , 0.000384 ),
935- "Fly Ash (kg/m3)" : (0.029577 , 0.000457 ),
936- "Slag (kg/m3)" : (0.085921 , 0.000326 ),
937- "Water (kg/m3)" : (0.001829 , 0.001177 ),
938- # HRWR GWP: consistent with published EPDs for polycarboxylate-based
939- # superplasticizers (1.5-5.0 kg CO₂/kg range; e.g. BASF MasterGlenium,
940- # Sika ViscoCrete). Tight std confirms deterministic upstream formula.
941- "HRWR (kg/m3)" : (3.184316 , 0.016840 ),
942- "Fine Aggregate (kg/m3)" : (0.002762 , 0.000113 ),
943- "Coarse Aggregates (kg/m3)" : (0.003895 , 0.000121 ),
944- "Temp (C)" : (0.002967 , 0.005397 ),
934+ "Cement (kg/m3)" : (0.762610 , 0.000365 ),
935+ "Fly Ash (kg/m3)" : (0.029601 , 0.000432 ),
936+ "Slag (kg/m3)" : (0.085926 , 0.000310 ),
937+ "Water (kg/m3)" : (- 0.001765 , 0.001114 ),
938+ "HRWR (kg/m3)" : (3.184692 , 0.016001 ),
939+ "Fine Aggregate (kg/m3)" : (0.002788 , 0.000097 ),
940+ "Coarse Aggregates (kg/m3)" : (0.003910 , 0.000112 ),
945941 },
946942 1 : { # Material Source 1
947- "Cement (kg/m3)" : (0.774398 , 0.007709 ),
948- "Fly Ash (kg/m3)" : (0.036826 , 0.005956 ),
949- "Slag (kg/m3)" : (0.094776 , 0.007031 ),
950- "Water (kg/m3)" : (0.001752 , 0.020415 ),
951- # HRWR GWP: consistent with Source 0 (3.18 vs 3.10 kg CO₂/kg).
943+ "Cement (kg/m3)" : (0.773814 , 0.007240 ),
944+ "Fly Ash (kg/m3)" : (0.035681 , 0.005542 ),
945+ "Slag (kg/m3)" : (0.092849 , 0.006469 ),
946+ "Water (kg/m3)" : (0.003864 , 0.019144 ),
947+ # HRWR GWP: consistent with Source 0 (3.18 vs 3.15 kg CO₂/kg).
952948 # See comment above for EPD references.
953- "HRWR (kg/m3)" : (3.102591 , 0.531138 ),
954- "Fine Aggregate (kg/m3)" : (0.002823 , 0.003714 ),
955- "Coarse Aggregates (kg/m3)" : (0.001063 , 0.003163 ),
956- "Temp (C)" : (0.072522 , 0.054983 ),
949+ "HRWR (kg/m3)" : (3.151231 , 0.498391 ),
950+ "Fine Aggregate (kg/m3)" : (0.002513 , 0.003486 ),
951+ "Coarse Aggregates (kg/m3)" : (- 0.000039 , 0.002870 ),
957952 },
958953}
959954
@@ -1016,31 +1011,40 @@ def make_linear_coefficients(
10161011 return means , variances
10171012
10181013
1019- def get_day_zero_data (X : Tensor , bounds : Tensor | None , n : int = 128 ):
1020- """Computes a tensor of n sobol points that satisfy the bounds, appended with a
1021- zeros tensor. Useful to condition the strength GP to be zero at day zero.
1014+ def get_day_zero_data (X : Tensor , bounds : Tensor | None = None , n : int = 128 ):
1015+ """Generates pseudo-observations at time=0 for conditioning the GP to predict
1016+ zero strength at day zero.
1017+
1018+ Uses the unique compositions from the training data (without time) to ensure
1019+ the constraint is enforced at all observed mix designs. If the number of unique
1020+ compositions exceeds n, a random subset is selected.
10221021
10231022 Args:
1024- X: The input tensor.
1025- bounds: The bounds of the input tensor. If None, will be inferred from X.
1026- n: The number of sobol points to generate.
1023+ X: The input tensor (n_train x d), where the last column is time.
1024+ bounds: Unused, kept for API compatibility. Will be removed in a future version.
1025+ n: Maximum number of pseudo-observations. If there are fewer unique
1026+ compositions than n, all unique compositions are used.
10271027
10281028 Returns:
1029- A tensor of n sobol points that satisfy the bounds, appended with a zeros
1030- tensor, corresponding to the strength at day zero.
1029+ A tuple (X_0, Y_0, Yvar_0) of pseudo-observations at time=0.
10311030 """
1032- if bounds is None :
1033- bounds = torch .stack ((X .amin (dim = 0 ), X .amax (dim = 0 )))
1031+ # Use unique observed compositions (without time)
1032+ unique_comps = torch .unique (X [:, :- 1 ], dim = 0 )
1033+ n_unique = unique_comps .shape [0 ]
10341034
1035- d = bounds .shape [- 1 ]
1036- sobol_engine = torch .quasirandom .SobolEngine (dimension = (d - 1 )) # excluding time
1037- X_0 = sobol_engine .draw (n )
1038- X_0 = torch .cat ((X_0 , torch .zeros (n , 1 )), dim = - 1 ) # append time (zero)
1039- a , b = bounds [0 ], bounds [1 ]
1040- X_0 = (b - a ) * X_0 + a # scaling according to bounds
1041- X_0 [:, - 1 ] = 0.0 # explicitly set time to zero (day zero conditioning)
1042- Y_0 = torch .zeros (n , 1 ) # zero strength
1043- Yvar_0 = torch .full ((n , 1 ), 1e-4 ) # with large certainty
1035+ if n_unique <= n :
1036+ # Use all unique compositions
1037+ X_comps = unique_comps
1038+ else :
1039+ # Random subset of unique compositions
1040+ perm = torch .randperm (n_unique )[:n ]
1041+ X_comps = unique_comps [perm ]
1042+
1043+ n_out = X_comps .shape [0 ]
1044+ # Append time=0
1045+ X_0 = torch .cat ((X_comps , torch .zeros (n_out , 1 , dtype = X .dtype )), dim = - 1 )
1046+ Y_0 = torch .zeros (n_out , 1 , dtype = X .dtype )
1047+ Yvar_0 = torch .full ((n_out , 1 ), 1e-4 , dtype = X .dtype )
10441048 return X_0 , Y_0 , Yvar_0
10451049
10461050
0 commit comments