2525from scipy .stats import gumbel_r
2626from torch .utils .data import DataLoader , RandomSampler , TensorDataset
2727
28+ torch .set_default_dtype (torch .float64 )
29+
2830# This allows us to run as interactive and as a module.
2931if __name__ == "__main__" :
3032 from simulator import _true_loc_func , _true_scale_func # type: ignore[import-not-found]
3133else :
3234 from .simulator import _true_loc_func , _true_scale_func
33-
34- torch .set_default_dtype (torch .float64 )
35-
3635# for typing
3736_ : Any
3837
@@ -49,12 +48,13 @@ class ResultsObject:
4948 # statistics are optional
5049 statistics : dict [str , float ]
5150 samples : list [float ]
51+ env_data : list [float ]
5252
5353 @classmethod
54- def from_samples (cls , samples : torch .Tensor ) -> "ResultsObject" :
54+ def from_samples (cls , samples : torch .Tensor , env_data : torch . Tensor ) -> "ResultsObject" :
5555 """Create the object directly from samples."""
5656 statistics = {"median" : float (samples .median ()), "mean" : float (samples .mean ())}
57- return ResultsObject (statistics = statistics , samples = samples .tolist ())
57+ return ResultsObject (statistics = statistics , samples = samples .tolist (), env_data = env_data . tolist () )
5858
5959
6060# %%
@@ -63,7 +63,7 @@ def _result_file_name(period_length: int) -> str:
6363 return f"n_sample_per_period_{ period_length } "
6464
6565
66- def collect_or_calculate_results (period_length : int , num_estimates : int = 2_000 ) -> torch .Tensor :
66+ def collect_or_calculate_results (period_length : int , num_estimates : int = 2_000 ) -> tuple [ torch .Tensor , torch . Tensor ] :
6767 """Return a saved result for the desired length of time if available, otherwise calculate the result.
6868
6969 New results will also be saved within this directory.
@@ -74,30 +74,40 @@ def collect_or_calculate_results(period_length: int, num_estimates: int = 2_000)
7474 num_estimates: The number of brute force estimates of the QoI. A new period is drawn for each estimate.
7575
7676 Returns:
77- The QoI values calculated for each period. Shape (num_estimates,)
77+ Tuple of:
78+ ERD samples: (num_estimates,) samples of the ERD for that period length. QoIs can be calculated from this.
79+ X_max: (num_estimates, d) The location in the environments space that produced the ERD sample.
7880 """
7981 results_path = _results_dir / f"{ _result_file_name (period_length )} .json"
8082
8183 samples = torch .tensor ([])
84+ max_location = torch .tensor ([])
8285
8386 if results_path .exists ():
8487 with results_path .open () as fp :
8588 results = json .load (fp )
8689 samples = torch .tensor (results ["samples" ])
90+ max_location = torch .tensor (results ["env_data" ])
8791
8892 # make any additional samples required
8993 if len (samples ) < num_estimates :
90- new_samples = brute_force (period_length , num_estimates - len (samples ))
94+ new_samples , new_max_location = brute_force (period_length , num_estimates - len (samples ))
9195
9296 samples = torch .concat ([samples , new_samples ])
97+ max_location = torch .concat ([max_location , new_max_location ])
98+
9399 # save results:
94100 with results_path .open ("w" ) as fp :
95- json .dump (asdict (ResultsObject .from_samples (samples )), fp )
101+ json .dump (asdict (ResultsObject .from_samples (samples , max_location )), fp )
96102
97- return samples
103+ elif len (samples ) > num_estimates :
104+ samples = samples [:num_estimates ]
105+ max_location = max_location [:num_estimates ]
98106
107+ return samples , max_location
99108
100- def brute_force (period_length : int , num_estimates : int = 2_000 ) -> torch .Tensor :
109+
110+ def brute_force (period_length : int , num_estimates : int = 2_000 ) -> tuple [torch .Tensor , torch .Tensor ]:
101111 """Produces brute force samples of the Extreme Response Distibtuion.
102112
103113 Args:
@@ -119,7 +129,9 @@ def brute_force(period_length: int, num_estimates: int = 2_000) -> torch.Tensor:
119129 return _brute_force_calc (dataloader , num_estimates )
120130
121131
122- def _brute_force_calc (dataloader : DataLoader [tuple [torch .Tensor , ...]], num_estimates : int = 2_000 ) -> torch .Tensor :
132+ def _brute_force_calc (
133+ dataloader : DataLoader [tuple [torch .Tensor , ...]], num_estimates : int = 2_000
134+ ) -> tuple [torch .Tensor , torch .Tensor ]:
123135 """Calculate the QOI by brute force.
124136
125137 Args:
@@ -131,9 +143,15 @@ def _brute_force_calc(dataloader: DataLoader[tuple[torch.Tensor, ...]], num_esti
131143 num_estimates: The number of brute force estimates of the QoI. A new period is drawn for each estimate.
132144
133145 Returns:
134- The QoI values calculated for each period. Shape (num_estimates,)
146+ Tuple of:
147+ ERD samples: (num_estimates,) samples of the ERD for that period length. QoIs can be calculated from this.
148+ X_max: (num_estimates, d) The location in the environments space that produced the ERD sample.
135149 """
136150 maxs = torch .zeros (num_estimates )
151+
152+ _ , d = next (iter (dataloader ))[0 ].shape
153+ maxs_location = torch .zeros (num_estimates , d )
154+
137155 for i in tqdm .tqdm (range (num_estimates )):
138156 current_max = float ("-inf" )
139157
@@ -144,11 +162,17 @@ def _brute_force_calc(dataloader: DataLoader[tuple[torch.Tensor, ...]], num_esti
144162
145163 gumbel_samples : np .ndarray [tuple [int ,], Any ] = gumbel_r .rvs (loc = loc , scale = scale ) # type: ignore # noqa: PGH003
146164
147- current_max = max (current_max , gumbel_samples .max ())
165+ simulator_samples_max = gumbel_samples .max ()
166+ if simulator_samples_max > current_max :
167+ current_max = simulator_samples_max
168+
169+ # Get env data corresponding to max(c_max)
170+ max_index = np .argmax (gumbel_samples )
171+ maxs_location [i ] = torch .tensor (samples [max_index , :])
148172
149173 maxs [i ] = current_max
150174
151- return maxs
175+ return maxs , maxs_location
152176
153177
154178# %%
@@ -161,7 +185,7 @@ def _brute_force_calc(dataloader: DataLoader[tuple[torch.Tensor, ...]], num_esti
161185 N_ENV_SAMPLES_PER_PERIOD = N_YEARS_IN_PERIOD * N_SECONDS_IN_YEAR // N_SECONDS_IN_TIME_STEP
162186 N_ENV_SAMPLES_PER_PERIOD = 1000
163187
164- samples = collect_or_calculate_results (N_ENV_SAMPLES_PER_PERIOD , 300_000 )
188+ samples , x_max = collect_or_calculate_results (N_ENV_SAMPLES_PER_PERIOD , 300_000 )
165189
166190 _ = plt .hist (samples , bins = 100 , density = True )
167191 _ = plt .title (
@@ -176,6 +200,9 @@ def _brute_force_calc(dataloader: DataLoader[tuple[torch.Tensor, ...]], num_esti
176200 )
177201 plt .show ()
178202
203+ _ = plt .scatter (x_max [:, 0 ], x_max [:, 1 ])
204+ plt .show ()
205+
179206 # %%
180207 """Here we explore the noise in the brute force result as more samples are collected.
181208
0 commit comments