Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Analysis results improvements #282

Merged
merged 34 commits into from
Nov 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
a4cd69d
Add get_sobol_second to PCEAnalysis Results
jlakhlili Nov 18, 2020
3f5f305
Update PCE tuto.
jlakhlili Nov 18, 2020
5907f6c
Merge branch 'dev' into sob-2nd
jlakhlili Nov 18, 2020
e100812
changes to sobols_total
orbitfold Nov 18, 2020
408f8c6
re-implemented sobols_first and sobols_total
orbitfold Nov 18, 2020
e96b7db
typo fix
orbitfold Nov 18, 2020
7cf3a65
updated doctstrings
orbitfold Nov 18, 2020
55870b0
added some argument checks to first and total sobol index methods in …
orbitfold Nov 18, 2020
6ce4cd4
fix some typos
orbitfold Nov 18, 2020
91d38cf
changes to the test
orbitfold Nov 18, 2020
96c946c
some updates
orbitfold Nov 18, 2020
ebbab52
changes to the test
orbitfold Nov 18, 2020
b2d4eba
added a test to check vector qoi resutls
orbitfold Nov 19, 2020
808c38b
updated docstring examples
orbitfold Nov 19, 2020
2bc508c
Merge pull request #281 from UCL-CCS/sob-2nd
orbitfold Nov 19, 2020
dfcc656
changes to sobols_total
orbitfold Nov 18, 2020
9d010d4
re-implemented sobols_first and sobols_total
orbitfold Nov 18, 2020
40777f0
typo fix
orbitfold Nov 18, 2020
95473c4
updated doctstrings
orbitfold Nov 18, 2020
fbc162c
added some argument checks to first and total sobol index methods in …
orbitfold Nov 18, 2020
c2e90ba
fix some typos
orbitfold Nov 18, 2020
6c2e6c6
changes to the test
orbitfold Nov 18, 2020
9ce4945
some updates
orbitfold Nov 18, 2020
6078328
changes to the test
orbitfold Nov 18, 2020
f9cfffd
added a test to check vector qoi resutls
orbitfold Nov 19, 2020
f7b3c35
updated docstring examples
orbitfold Nov 19, 2020
0e1b436
Merge branch 'analysis-results-improvements' of https://github.com/UC…
orbitfold Nov 19, 2020
5292870
updated results analysis in pce
orbitfold Nov 19, 2020
3792e8b
work on second order sobol index reporting
orbitfold Nov 19, 2020
f0e5abb
updated sobols reporting
orbitfold Nov 19, 2020
15b2238
updated the tests
orbitfold Nov 19, 2020
2646642
remove unreachable code
orbitfold Nov 20, 2020
c5029d2
pep8
orbitfold Nov 20, 2020
3837213
fix test_mc_analysis_results.py
orbitfold Nov 20, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
41 changes: 19 additions & 22 deletions easyvvuq/analysis/pce_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ def _get_sobols_first(self, qoi, input_):
First order sobol index.
"""
raw_dict = AnalysisResults._keys_to_tuples(self.raw_data['sobols_first'])
return raw_dict[AnalysisResults._to_tuple(qoi)][input_][0]
return raw_dict[AnalysisResults._to_tuple(qoi)][input_][0][0]

def _get_sobols_total(self, qoi, input_):
"""Returns the total order sobol index for a given qoi wrt input variable.
def _get_sobols_second(self, qoi, input_):
"""Returns the second order sobol index for a given qoi wrt input variable.

Parameters
----------
Expand All @@ -48,32 +48,29 @@ def _get_sobols_total(self, qoi, input_):
Returns
-------
float
Total order sobol index.
Second order sobol index.
"""
raw_dict = AnalysisResults._keys_to_tuples(self.raw_data['sobols_total'])
return raw_dict[AnalysisResults._to_tuple(qoi)][input_][0]

def _get_sobols_first_conf(self, qoi, input_):
"""Not implemented for this method.
raw_dict = AnalysisResults._keys_to_tuples(self.raw_data['sobols_second'])
return dict([(in_, raw_dict[AnalysisResults._to_tuple(qoi)][input_][0][i][0][0])
for i, in_ in enumerate(self.inputs)])

Returns
-------
list of floats
Will return a list with two nans, since this is
pandas way for handling missing values it seems.
"""
return [float('nan'), float('nan')]
def _get_sobols_total(self, qoi, input_):
"""Returns the total order sobol index for a given qoi wrt input variable.

def _get_sobols_total_conf(self, qoi, input_):
"""Not implemented for this method.
Parameters
----------
qoi : str
Quantity of interest
input_ : str
Input variable

Returns
-------
list of floats
Will return a list with two nans, since this is
pandas way for handling missing values it seems.
float
Total order sobol index.
"""
return [float('nan'), float('nan')]
raw_dict = AnalysisResults._keys_to_tuples(self.raw_data['sobols_total'])
return raw_dict[AnalysisResults._to_tuple(qoi)][input_][0][0]

def describe(self):
result = {}
Expand Down
3 changes: 3 additions & 0 deletions easyvvuq/analysis/qmc_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ def _get_sobols_total(self, qoi, input_):
raw_dict = AnalysisResults._keys_to_tuples(self.raw_data['sobols_total'])
return raw_dict[AnalysisResults._to_tuple(qoi)][input_][0]

def _get_sobols_second(self, qoi, input_):
raise NotImplementedError

def _get_sobols_first_conf(self, qoi, input_):
raw_dict = AnalysisResults._keys_to_tuples(self.raw_data['conf_sobols_first'])
return [raw_dict[AnalysisResults._to_tuple(qoi)][input_]['low'][0],
Expand Down
163 changes: 97 additions & 66 deletions easyvvuq/analysis/results.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,90 +119,121 @@ def _get_sobols_total_conf(self, qoi, input_):
"""
raise NotImplementedError

def sobols_first(self):
"""Creates a pandas DataFrame with the first order sensitivity
indices.
def _get_sobols_general(self, getter, qoi=None, input_=None):
"""A generic method for getting sobol indices.

The row indices are of the form (qoi, i) where qoi is the name
of the quantity of interest and i is the coordinate index
(will be zero for scalar qois).
Parameters
----------
getter: function
Method that takes a AnalysisResults instance and returns
a Sobol index of some kind. For example _get_bonols_first.

qoi: str or tuple
The name of the quantity of interest or None.
Use a tuple of the form (qoi, index) where index is integer
that means the coordinate index of a vector qoi.

The columns indices are of the form (input, i, {low, est,
high}) where input is the name of the input variable, i is the
coordinate provided it is a vector quantitty and the last
element of the 3-tuple is a string which specifies whether it
is the estimator value for the first order sensitivity index
or lower or higher 95% confidence interval values.
input_: str
The name of the input parameter or None.

Returns
-------
a dictionary or an array
"""
assert(not ((qoi is None) and (input_ is not None)))
if (qoi is not None) and (qoi not in self.qois):
raise RuntimeError('no such qoi in this analysis')
if (input_ is not None) and (input_ not in self.inputs):
raise RuntimeError('no such input variable in this analysis')
try:
if input_ is None:
if qoi is None:
return dict([(qoi_, dict([(in_, getter(qoi_, in_))
for in_ in self.inputs]))
for qoi_ in self.qois])
else:
return dict([(in_, getter(qoi, in_))
for in_ in self.inputs])
else:
return getter(qoi, input_)
except NotImplementedError:
raise RuntimeError(
'this kind of sobol index reporting not implemented in this analysis method')

def sobols_first(self, qoi=None, input_=None):
"""Return first order sensitivity indices.

Parameters
----------
qoi: str or tuple
The name of the quantity of interest or None.
Use a tuple of the form (qoi, index) where index is integer
that means the coordinate index of a vector qoi.

input_: str
The name of the input parameter or None.

Examples
--------
>>> results.sobols_first()
x1 x2
0 0
est high low est high low
f 0 0.556906 0.894288 0.14387 0.207276 0.467528 -0.110633
{'f': {'x1': array([0.610242]), 'x2': array([0.26096511])}}
>>> results.sobols_first('f')
{'x1': array([0.610242]), 'x2': array([0.26096511])}
>>> results.sobols_first('f', 'x1')
array([0.610242])
>>> results_vectors.sobols_first(('g', 2))
{'x1': array([0.5]), 'x2': array([0.5])}

Returns
-------
a pandas DataFrame

a dictionary or an array
"""
df = {}
for qoi in self.qois:
qoi = AnalysisResults._to_tuple(qoi)
rows = {}
for input_ in self.inputs:
key = AnalysisResults._to_tuple(input_)
key_1 = key + ('low',)
key_2 = key + ('est',)
key_3 = key + ('high',)
rows[key_1] = self._get_sobols_first_conf(qoi, input_)[0]
rows[key_2] = self._get_sobols_first(qoi, input_)
rows[key_3] = self._get_sobols_first_conf(qoi, input_)[1]
df[qoi] = rows
return pd.DataFrame(df).T

def sobols_total(self):
"""Creates a pandas DataFrame with the total order sensitivity indices.

The row indices are of the form (qoi, i) where qoi is the name
of the quantity of interest and i is the coordinate index
(will be zero for scalar qois).

The columns indices are of the form (input, i, {low, est,
high}) where input is the name of the input variable, i is the
coordinate provided it is a vector quantitty and the last
element of the 3-tuple is a string which specifies whether it
is the estimator value for the first order sensitivity index
or lower or higher 95% confidence interval values.
return self._get_sobols_general(self._get_sobols_first, qoi, input_)

def sobols_second(self, qoi=None, input_=None):
"""Return second order sensitivity indices.

Parameters
----------
qoi: str or tuple
The name of the quantity of interest or None.
Use a tuple of the form (qoi, index) where index is integer
that means the coordinate index of a vector qoi.

input_: str
The name of the input parameter or None.

Examples
--------
>>> results.sobols_total()
x1 x2
0 0
est high low est high low
f 0 0.813279 1.018587 0.613689 0.380496 0.492141 0.243612

Returns
-------
a dictionary or an array
"""
return self._get_sobols_general(self._get_sobols_second, qoi, input_)

def sobols_total(self, qoi=None, input_=None):
"""Returns total order sensitivity indices.

Parameters
----------
qoi: str or tuple
The name of the quantity of interest or None.
Use a tuple of the form (qoi, index) where index is integer
that means the coordinate index of a vector qoi.

input_: str
The name of the input parameter or None.


Examples
--------

Returns
-------
a pandas DataFrame
a dictionary or an array
"""
df = {}
for qoi in self.qois:
qoi = AnalysisResults._to_tuple(qoi)
rows = {}
for input_ in self.inputs:
key = AnalysisResults._to_tuple(input_)
key_1 = key + ('low',)
key_2 = key + ('est',)
key_3 = key + ('high',)
rows[key_1] = self._get_sobols_total_conf(qoi, input_)[0]
rows[key_2] = self._get_sobols_total(qoi, input_)
rows[key_3] = self._get_sobols_total_conf(qoi, input_)[1]
df[qoi] = rows
return pd.DataFrame(df).T
return self._get_sobols_general(self._get_sobols_total, qoi, input_)

def surrogate(self):
"""Returns the surrogate model as a function from parameter dictionary
Expand Down
11 changes: 5 additions & 6 deletions easyvvuq/analysis/sc_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,13 @@


class SCAnalysisResults(AnalysisResults):
implemented = ['sobols_first', 'describe']

def _get_sobols_first(self, qoi, input_):
raw_dict = AnalysisResults._keys_to_tuples(self.raw_data['sobols_first'])
return raw_dict[AnalysisResults._to_tuple(qoi)][input_][0]

def _get_sobols_first_conf(self, qoi, input_):
return [float('nan'), float('nan')]
result = raw_dict[AnalysisResults._to_tuple(qoi)][input_][0]
try:
return np.array([float(result)])
except TypeError:
return np.array(result)

def describe(self):
result = {}
Expand Down
4 changes: 2 additions & 2 deletions tests/test_mc_analysis_results.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ def test_results_conf(results):


def test_full_results(results):
assert(results.sobols_first().shape == (1, 6))
assert(results.sobols_total().shape == (1, 6))
assert(results.sobols_first() == {'f': {'x1': 0.5569058947880715, 'x2': 0.20727553481694053}})
assert(results.sobols_total() == {'f': {'x1': 0.8132793654841785, 'x2': 0.3804962894947435}})


def test_describe(results):
Expand Down
63 changes: 46 additions & 17 deletions tests/test_pce_analysis_results.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,27 @@ def data():
return sampler, df


@pytest.fixture
def data_vectors():
np.random.seed(10000000)
vary = {
"x1": cp.Uniform(0.0, 1.0),
"x2": cp.Uniform(0.0, 1.0)
}
sampler = uq.sampling.SCSampler(vary)
data = {('run_id', 0): [], ('x1', 0): [], ('x2', 0): [],
('g', 0): [], ('g', 1): [], ('g', 2): []}
for run_id, sample in enumerate(sampler):
data[('run_id', 0)].append(run_id)
data[('x1', 0)].append(sample['x1'])
data[('x2', 0)].append(sample['x2'])
data[('g', 0)].append(sample['x1'])
data[('g', 1)].append(sample['x2'])
data[('g', 2)].append(sample['x1'] + sample['x2'])
df = pd.DataFrame(data)
return sampler, df


@pytest.fixture
def results(data):
# Post-processing analysis
Expand All @@ -51,36 +72,44 @@ def results(data):
return results


@pytest.fixture
def results_vectors(data_vectors):
# Post-processing analysis
sampler, df = data_vectors
analysis = uq.analysis.SCAnalysis(sampler=sampler, qoi_cols=[('g', 0), ('g', 1), ('g', 2)])
results = analysis.analyse(df)
return results


def test_results(results):
assert(isinstance(results, PCEAnalysisResults))
sobols_first_x1 = results._get_sobols_first('f', 'x1')
sobols_first_x2 = results._get_sobols_first('f', 'x2')
sobols_second_x1 = results._get_sobols_second('f', 'x1')
sobols_second_x2 = results._get_sobols_second('f', 'x2')
sobols_total_x1 = results._get_sobols_total('f', 'x1')
sobols_total_x2 = results._get_sobols_total('f', 'x2')
assert(sobols_first_x1 == pytest.approx(0.62644867, 0.001))
assert(sobols_first_x2 == pytest.approx(0.26789576, 0.001))
assert(sobols_second_x1 == {'x1': 0.0, 'x2': 0.10565556484738273})
assert(sobols_second_x2 == {'x1': 0.10565556484738273, 'x2': 0.0})
assert(sobols_total_x1 == pytest.approx(0.73210424, 0.001))
assert(sobols_total_x2 == pytest.approx(0.37355133, 0.001))


def test_results_conf(results):
sobols_first_x1_conf = results._get_sobols_first_conf('f', 'x1')
assert(math.isnan(sobols_first_x1_conf[0]))
assert(math.isnan(sobols_first_x1_conf[1]))
sobols_first_x2_conf = results._get_sobols_first_conf('f', 'x2')
assert(math.isnan(sobols_first_x2_conf[0]))
assert(math.isnan(sobols_first_x2_conf[1]))
sobols_total_x1_conf = results._get_sobols_total_conf('f', 'x1')
assert(math.isnan(sobols_total_x1_conf[0]))
assert(math.isnan(sobols_total_x1_conf[1]))
sobols_total_x2_conf = results._get_sobols_total_conf('f', 'x2')
assert(math.isnan(sobols_total_x2_conf[0]))
assert(math.isnan(sobols_total_x2_conf[1]))


def test_full_results(results):
assert(results.sobols_first().shape == (1, 6))
assert(results.sobols_total().shape == (1, 6))
with pytest.raises(RuntimeError):
results.sobols_first('z')
with pytest.raises(RuntimeError):
results.sobols_first('f', 'y')
with pytest.raises(AssertionError):
results.sobols_first(None, 'x1')
assert(results.sobols_first()['f']['x1'][0] == pytest.approx(0.6264486733708418))
assert(results.sobols_first()['f']['x2'][0] == pytest.approx(0.2678957617817755))
assert(results.sobols_first('f')['x1'][0] == pytest.approx(0.6264486733708418))
assert(results.sobols_first('f')['x2'][0] == pytest.approx(0.2678957617817755))
assert(results.sobols_first('f', 'x1')[0] == pytest.approx(0.6264486733708418))
assert(results.sobols_first('f', 'x2')[0] == pytest.approx(0.2678957617817755))


def test_describe(results):
Expand Down