From 7a217337446c13d67b4825593fffdc468538c1be Mon Sep 17 00:00:00 2001 From: Daniel Dias Date: Sat, 16 Nov 2024 15:00:58 -0300 Subject: [PATCH 01/19] update(interval): updating interval analysis to consider state bounds initialization and different strategies to compute distribution bounds --- .gitignore | 3 + pyRDDLGym/core/intervals.py | 205 +++++++++++++++++++++++++++++++++--- 2 files changed, 195 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index f773fd9f..518df17d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ *.pyc +build/ +*.egg-info/ + .project .pydevproject .settings/ diff --git a/pyRDDLGym/core/intervals.py b/pyRDDLGym/core/intervals.py index 16901cc8..a54f35f5 100644 --- a/pyRDDLGym/core/intervals.py +++ b/pyRDDLGym/core/intervals.py @@ -1,5 +1,9 @@ import numpy as np -from typing import Dict, List, Optional, Set, Tuple, Union +from scipy.special import gamma +import scipy.stats as stats + +from typing import Dict, Optional, Tuple +from enum import Enum Bounds = Dict[str, Tuple[np.ndarray, np.ndarray]] @@ -17,17 +21,38 @@ from pyRDDLGym.core.simulator import lngamma +class IntervalAnalysisStrategy(Enum): + SUPPORT = 1 + PERCENTILE = 2 + MEAN = 3 + class RDDLIntervalAnalysis: - def __init__(self, rddl: RDDLPlanningModel, logger: Optional[Logger]=None) -> None: + def __init__( + self, + rddl: RDDLPlanningModel, + logger: Optional[Logger]=None, + strategy: Optional[IntervalAnalysisStrategy]=IntervalAnalysisStrategy.SUPPORT, + percentiles: Optional[Tuple[float, float]]=None + ) -> None: '''Creates a new interval analysis object for the given RDDL domain. :param rddl: the RDDL domain to analyze :param logger: to log compilation information during tracing to file + :param strategy: strategy used to compute bounds on fluents that has stochastic components + :param percentiles: percentiles used to compute bounds when strategy is set to PERCENTILE ''' self.rddl = rddl self.logger = logger + self.strategy = strategy + self.percentiles = percentiles + + if self.strategy == IntervalAnalysisStrategy.PERCENTILE: + lower, upper = self.percentiles + if lower < 0 or lower > 1 or upper < 0 or upper > 1 or lower > upper: + raise ValueError('Percentiles must be in the range [0, 1] and lower <= upper.') + sorter = RDDLLevelAnalysis(rddl, allow_synchronous_state=True, logger=self.logger) self.cpf_levels = sorter.compute_levels() @@ -38,8 +63,10 @@ def __init__(self, rddl: RDDLPlanningModel, logger: Optional[Logger]=None) -> No self.NUMPY_OR_FUNC = np.frompyfunc(self._bound_or_scalar, nin=2, nout=1) self.NUMPY_LITERAL_TO_INT = np.vectorize(self.rddl.object_to_index.__getitem__) - def bound(self, action_bounds: Optional[Bounds]=None, - per_epoch: bool=False) -> Bounds: + def bound(self, + action_bounds: Optional[Bounds]=None, + per_epoch: bool=False, + state_bounds: Optional[Bounds]=None) -> Bounds: '''Computes intervals on all fluents and reward for the planning problem. :param action_bounds: optional bounds on action fluents (defaults to @@ -47,10 +74,12 @@ def bound(self, action_bounds: Optional[Bounds]=None, :param per_epoch: if True, the returned bounds are tensors with leading dimension indicating the decision epoch; if False, the returned bounds are valid across all decision epochs. + :param state_bounds: optional bounds on state fluents (defaults to + the initial state values otherwise) ''' # get initial values as bounds - intervals = self._bound_initial_values() + intervals = self._bound_initial_values(state_bounds) if per_epoch: result = {} @@ -72,7 +101,7 @@ def bound(self, action_bounds: Optional[Bounds]=None, else: return intervals - def _bound_initial_values(self): + def _bound_initial_values(self, state_bounds=None): rddl = self.rddl # initially all bounds are calculated based on the initial values @@ -90,7 +119,11 @@ def _bound_initial_values(self): params = rddl.variable_params[name] shape = rddl.object_counts(params) values = np.reshape(values, newshape=shape) - intervals[name] = (values, values) + if state_bounds is not None and name in state_bounds: + intervals[name] = state_bounds[name] + else: + intervals[name] = (values, values) + return intervals def _bound_next_epoch(self, intervals, action_bounds=None, per_epoch=False): @@ -158,10 +191,11 @@ def _bound(self, expr, intervals): result = self._bound_control(expr, intervals) elif etype == 'randomvar': result = self._bound_random(expr, intervals) - elif etype == 'randomvector': - result = self._bound_random_vector(expr, intervals) - elif etype == 'matrix': - result = self._bound_matrix(expr, intervals) + # TODO: methods not implemented yet + # elif etype == 'randomvector': + # result = self._bound_random_vector(expr, intervals) + # elif etype == 'matrix': + # result = self._bound_matrix(expr, intervals) else: raise RDDLNotImplementedError( f'Internal error: expression type {etype} is not supported.\n' + @@ -240,6 +274,13 @@ def _bound_pvar(self, expr, intervals): @staticmethod def _mask_assign(dest, mask, value, mask_value=False): + '''Assings a value to a destination array based on a mask. + + :param dest: the destination array to assign to + :param mask: the mask array to determine where to assign + :param value: the value to assign + :param mask_value: if True, the value is also masked + ''' assert (np.shape(dest) == np.shape(mask)) if np.shape(dest): if mask_value: @@ -898,11 +939,20 @@ def _bound_random(self, expr, intervals): def _bound_random_kron(self, expr, intervals): args = expr.args arg, = args + + if self.strategy == IntervalAnalysisStrategy.PERCENTILE: + raise NotImplementedError("Percentile strategy is not implemented for Kronecker delta distribution yet.") + + if self.strategy == IntervalAnalysisStrategy.MEAN: + raise NotImplementedError("Mean strategy is not implemented for Kronecker delta distribution yet.") + return self._bound(arg, intervals) def _bound_random_dirac(self, expr, intervals): args = expr.args arg, = args + + # SUPPORT, PERCENTILE or MEAN strategy return self._bound(arg, intervals) def _bound_uniform(self, expr, intervals): @@ -910,6 +960,13 @@ def _bound_uniform(self, expr, intervals): a, b = args (la, ua) = self._bound(a, intervals) (lb, ub) = self._bound(b, intervals) + + if self.strategy == IntervalAnalysisStrategy.PERCENTILE: + raise NotImplementedError("Percentile strategy is not implemented for Uniform distribution yet.") + + if self.strategy == IntervalAnalysisStrategy.MEAN: + raise NotImplementedError("Mean strategy is not implemented for Uniform distribution yet.") + lower = la upper = ub return (lower, upper) @@ -919,6 +976,21 @@ def _bound_bernoulli(self, expr, intervals): p, = args (lp, up) = self._bound(p, intervals) + if self.strategy == IntervalAnalysisStrategy.PERCENTILE: + lower_percentile, upper_percentile = self.percentiles + + lower = np.zeros(shape=np.shape(lp), dtype=np.int64) + upper = np.ones(shape=np.shape(up), dtype=np.int64) + + lower = self._mask_assign(lower, lp >= lower_percentile, 1) + upper = self._mask_assign(upper, up <= upper_percentile, 0) + + return (lower, upper) + + if self.strategy == IntervalAnalysisStrategy.MEAN: + return (lp, up) + + # SUPPORT strategy lower = np.zeros(shape=np.shape(lp), dtype=np.int64) upper = np.ones(shape=np.shape(up), dtype=np.int64) lower = self._mask_assign(lower, lp >= 1, 1) @@ -931,6 +1003,19 @@ def _bound_normal(self, expr, intervals): (lm, um) = self._bound(mean, intervals) (lv, uv) = self._bound(var, intervals) + if self.strategy == IntervalAnalysisStrategy.PERCENTILE: + # mean + std * normal_inverted_cdf(p) + lower_percentile, upper_percentile = self.percentiles + + lower = lm * np.sqrt(lv) * stats.norm.ppf(lower_percentile) + upper = um * np.sqrt(uv) * stats.norm.ppf(upper_percentile) + return (lower, upper) + + if self.strategy == IntervalAnalysisStrategy.MEAN: + # mean + return (lm, um) + + # SUPPORT strategy lower = np.full(shape=np.shape(lm), fill_value=-np.inf, dtype=np.float64) upper = np.full(shape=np.shape(um), fill_value=+np.inf, dtype=np.float64) lower = self._mask_assign(lower, (lv == 0) & (uv == 0), lm, True) @@ -942,6 +1027,12 @@ def _bound_poisson(self, expr, intervals): p, = args (lp, up) = self._bound(p, intervals) + if self.strategy == IntervalAnalysisStrategy.PERCENTILE: + raise NotImplementedError("Percentile strategy is not implemented for Poisson distribution yet.") + + if self.strategy == IntervalAnalysisStrategy.MEAN: + raise NotImplementedError("Mean strategy is not implemented for Poisson distribution yet.") + lower = np.zeros(shape=np.shape(lp), dtype=np.int64) upper = np.full(shape=np.shape(up), fill_value=np.inf, dtype=np.float64) return (lower, upper) @@ -951,6 +1042,12 @@ def _bound_exponential(self, expr, intervals): scale, = args (ls, us) = self._bound(scale, intervals) + if self.strategy == IntervalAnalysisStrategy.PERCENTILE: + raise NotImplementedError("Percentile strategy is not implemented for Exponential distribution yet.") + + if self.strategy == IntervalAnalysisStrategy.MEAN: + raise NotImplementedError("Mean strategy is not implemented for Exponential distribution yet.") + lower = np.zeros(shape=np.shape(ls), dtype=np.float64) upper = np.full(shape=np.shape(us), fill_value=np.inf, dtype=np.float64) return (lower, upper) @@ -961,6 +1058,21 @@ def _bound_weibull(self, expr, intervals): (lsh, ush) = self._bound(shape, intervals) (lsc, usc) = self._bound(scale, intervals) + if self.strategy == IntervalAnalysisStrategy.PERCENTILE: + # scale * (-ln(1 - p))^(1 / shape) + lower_percentile, upper_percentile = self.percentiles + + lower = lsc * (-np.log(1 - lower_percentile) ) ** (1 / lsh) + upper = usc * (-np.log(1 - upper_percentile) ) ** (1 / ush) + return (lower, upper) + + if self.strategy == IntervalAnalysisStrategy.MEAN: + # scale * gamma(1 + 1 / shape) + lower = lsc * gamma(1 + 1 / lsh) + upper = usc * gamma(1 + 1 / ush) + return (lower, upper) + + # SUPPORT strategy lower = np.zeros(shape=np.shape(lsh), dtype=np.float64) upper = np.full(shape=np.shape(ush), fill_value=np.inf, dtype=np.float64) return (lower, upper) @@ -971,8 +1083,14 @@ def _bound_gamma(self, expr, intervals): (lsh, ush) = self._bound(shape, intervals) (lsc, usc) = self._bound(scale, intervals) - lower = np.zeros(shape=np.shape(ls), dtype=np.float64) - upper = np.full(shape=np.shape(us), fill_value=np.inf, dtype=np.float64) + if self.strategy == IntervalAnalysisStrategy.PERCENTILE: + raise NotImplementedError("Percentile strategy is not implemented for Gamma distribution yet.") + + if self.strategy == IntervalAnalysisStrategy.MEAN: + raise NotImplementedError("Mean strategy is not implemented for Gamma distribution yet.") + + lower = np.zeros(shape=np.shape(lsh), dtype=np.float64) + upper = np.full(shape=np.shape(usc), fill_value=np.inf, dtype=np.float64) return (lower, upper) def _bound_binomial(self, expr, intervals): @@ -981,6 +1099,12 @@ def _bound_binomial(self, expr, intervals): (ln, un) = self._bound(n, intervals) (lp, up) = self._bound(p, intervals) + if self.strategy == IntervalAnalysisStrategy.PERCENTILE: + raise NotImplementedError("Percentile strategy is not implemented for Binomial distribution yet.") + + if self.strategy == IntervalAnalysisStrategy.MEAN: + raise NotImplementedError("Mean strategy is not implemented for Binomial distribution yet.") + lower = np.zeros(shape=np.shape(ln), dtype=np.int64) upper = np.copy(un) lower = self._mask_assign(lower, (lp >= 1) & (ln > 0), ln, True) @@ -993,6 +1117,12 @@ def _bound_beta(self, expr, intervals): (ls, us) = self._bound(shape, intervals) (lr, ur) = self._bound(rate, intervals) + if self.strategy == IntervalAnalysisStrategy.PERCENTILE: + raise NotImplementedError("Percentile strategy is not implemented for Beta distribution yet.") + + if self.strategy == IntervalAnalysisStrategy.MEAN: + raise NotImplementedError("Mean strategy is not implemented for Beta distribution yet.") + lower = np.zeros(shape=np.shape(ls), dtype=np.float64) upper = np.ones(shape=np.shape(us), dtype=np.float64) return (lower, upper) @@ -1002,6 +1132,13 @@ def _bound_geometric(self, expr, intervals): p, = args (lp, up) = self._bound(p, intervals) + + if self.strategy == IntervalAnalysisStrategy.PERCENTILE: + raise NotImplementedError("Percentile strategy is not implemented for Geometric distribution yet.") + + if self.strategy == IntervalAnalysisStrategy.MEAN: + raise NotImplementedError("Mean strategy is not implemented for Geometric distribution yet.") + lower = np.ones(shape=np.shape(lp), dtype=np.int64) upper = np.full(shape=np.shape(up), fill_value=np.inf, dtype=np.float64) return (lower, upper) @@ -1012,6 +1149,12 @@ def _bound_pareto(self, expr, intervals): (lsh, ush) = self._bound(shape, intervals) (lsc, usc) = self._bound(scale, intervals) + if self.strategy == IntervalAnalysisStrategy.PERCENTILE: + raise NotImplementedError("Percentile strategy is not implemented for Pareto distribution yet.") + + if self.strategy == IntervalAnalysisStrategy.MEAN: + raise NotImplementedError("Mean strategy is not implemented for Pareto distribution yet.") + lower = lsc upper = np.full(shape=np.shape(usc), fill_value=np.inf, dtype=np.float64) return (lower, upper) @@ -1021,6 +1164,12 @@ def _bound_student(self, expr, intervals): df, = args (ld, ud) = self._bound(df, intervals) + if self.strategy == IntervalAnalysisStrategy.PERCENTILE: + raise NotImplementedError("Percentile strategy is not implemented for Student distribution yet.") + + if self.strategy == IntervalAnalysisStrategy.MEAN: + raise NotImplementedError("Mean strategy is not implemented for Student distribution yet.") + lower = np.full(shape=np.shape(ld), fill_value=-np.inf, dtype=np.float64) upper = np.full(shape=np.shape(ud), fill_value=+np.inf, dtype=np.float64) return (lower, upper) @@ -1031,6 +1180,12 @@ def _bound_gumbel(self, expr, intervals): (lm, um) = self._bound(mean, intervals) (ls, us) = self._bound(scale, intervals) + if self.strategy == IntervalAnalysisStrategy.PERCENTILE: + raise NotImplementedError("Percentile strategy is not implemented for Gumbel distribution yet.") + + if self.strategy == IntervalAnalysisStrategy.MEAN: + raise NotImplementedError("Mean strategy is not implemented for Gumbel distribution yet.") + lower = np.full(shape=np.shape(lm), fill_value=-np.inf, dtype=np.float64) upper = np.full(shape=np.shape(um), fill_value=+np.inf, dtype=np.float64) return (lower, upper) @@ -1041,6 +1196,12 @@ def _bound_cauchy(self, expr, intervals): (lm, um) = self._bound(mean, intervals) (ls, us) = self._bound(scale, intervals) + if self.strategy == IntervalAnalysisStrategy.PERCENTILE: + raise NotImplementedError("Percentile strategy is not implemented for Cauchy distribution yet.") + + if self.strategy == IntervalAnalysisStrategy.MEAN: + raise NotImplementedError("Mean strategy is not implemented for Cauchy distribution yet.") + lower = np.full(shape=np.shape(lm), fill_value=-np.inf, dtype=np.float64) upper = np.full(shape=np.shape(um), fill_value=+np.inf, dtype=np.float64) return (lower, upper) @@ -1051,6 +1212,12 @@ def _bound_gompertz(self, expr, intervals): (lsh, ush) = self._bound(shape, intervals) (lsc, usc) = self._bound(scale, intervals) + if self.strategy == IntervalAnalysisStrategy.PERCENTILE: + raise NotImplementedError("Percentile strategy is not implemented for Gompertz distribution yet.") + + if self.strategy == IntervalAnalysisStrategy.MEAN: + raise NotImplementedError("Mean strategy is not implemented for Gompertz distribution yet.") + lower = np.zeros(shape=np.shape(lsh), dtype=np.float64) upper = np.full(shape=np.shape(ush), fill_value=np.inf, dtype=np.float64) return (lower, upper) @@ -1060,6 +1227,12 @@ def _bound_chisquare(self, expr, intervals): df, = args (ld, ud) = self._bound(df, intervals) + if self.strategy == IntervalAnalysisStrategy.PERCENTILE: + raise NotImplementedError("Percentile strategy is not implemented for Chi-square distribution yet.") + + if self.strategy == IntervalAnalysisStrategy.MEAN: + raise NotImplementedError("Mean strategy is not implemented for Chi-square distribution yet.") + lower = np.zeros(shape=np.shape(ld), dtype=np.float64) upper = np.full(shape=np.shape(ud), fill_value=np.inf, dtype=np.float64) return (lower, upper) @@ -1070,6 +1243,12 @@ def _bound_kumaraswamy(self, expr, intervals): (la, ua) = self._bound(a, intervals) (lb, ub) = self._bound(b, intervals) + if self.strategy == IntervalAnalysisStrategy.PERCENTILE: + raise NotImplementedError("Percentile strategy is not implemented for Kumaraswamy distribution yet.") + + if self.strategy == IntervalAnalysisStrategy.MEAN: + raise NotImplementedError("Mean strategy is not implemented for Kumaraswamy distribution yet.") + lower = np.zeros(shape=np.shape(la), dtype=np.float64) upper = np.ones(shape=np.shape(ua), dtype=np.float64) return (lower, upper) From 81501929310f66bc28e21a161f605b3d26c8b386 Mon Sep 17 00:00:00 2001 From: Daniel Dias Date: Sat, 16 Nov 2024 15:13:09 -0300 Subject: [PATCH 02/19] add github actions --- .github/workflows/unit-tests.yaml | 25 +++++++++++++++++++++++++ pyRDDLGym/core/intervals.py | 5 +---- 2 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/unit-tests.yaml diff --git a/.github/workflows/unit-tests.yaml b/.github/workflows/unit-tests.yaml new file mode 100644 index 00000000..04afc536 --- /dev/null +++ b/.github/workflows/unit-tests.yaml @@ -0,0 +1,25 @@ +name: Run unit tests + +on: + push: + branches: [main] + paths-ignore: + - 'docs/**' + pull_request: + +jobs: + unit-test: + name: Run unit tests over pyRDDLGym + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Setup python version + uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Install dependencies + run: pip install -r requirements.txt \ No newline at end of file diff --git a/pyRDDLGym/core/intervals.py b/pyRDDLGym/core/intervals.py index a54f35f5..ccb330ba 100644 --- a/pyRDDLGym/core/intervals.py +++ b/pyRDDLGym/core/intervals.py @@ -1277,7 +1277,4 @@ def _bound_discrete_pvar(self, expr, intervals, unnorm): lower_prob, upper_prob = self._bound(arg, intervals) bounds = [(lower_prob[..., i], upper_prob[..., i]) for i in range(lower_prob.shape[-1])] - return self._bound_discrete_helper(bounds) - - - \ No newline at end of file + return self._bound_discrete_helper(bounds) \ No newline at end of file From 22acb82d02758fa8fe5ac8835a6882cdf0b4ee23 Mon Sep 17 00:00:00 2001 From: Daniel Dias Date: Sat, 16 Nov 2024 15:20:44 -0300 Subject: [PATCH 03/19] update ghactions --- .github/workflows/unit-tests.yaml | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/.github/workflows/unit-tests.yaml b/.github/workflows/unit-tests.yaml index 04afc536..f3115f40 100644 --- a/.github/workflows/unit-tests.yaml +++ b/.github/workflows/unit-tests.yaml @@ -2,24 +2,33 @@ name: Run unit tests on: push: - branches: [main] + # branches: [main] paths-ignore: - 'docs/**' + - 'Images/**' pull_request: jobs: unit-test: name: Run unit tests over pyRDDLGym runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] steps: - name: Checkout code - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - name: Setup python version + uses: actions/checkout@v4 + - name: Setup Python version ${{ matrix.python-version }} uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: ${{ matrix.python-version }} - name: Install dependencies - run: pip install -r requirements.txt \ No newline at end of file + run: | + python -m pip install --upgrade pip + python -m pip install pytest + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Test with pytest + run: | + pytest \ No newline at end of file From 4fbf1861a490f6f104aedba182886c13cd8ff1b2 Mon Sep 17 00:00:00 2001 From: Daniel Dias Date: Sat, 16 Nov 2024 15:30:09 -0300 Subject: [PATCH 04/19] testing gh actions --- .github/workflows/unit-tests.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/unit-tests.yaml b/.github/workflows/unit-tests.yaml index f3115f40..d175251f 100644 --- a/.github/workflows/unit-tests.yaml +++ b/.github/workflows/unit-tests.yaml @@ -20,15 +20,18 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + - name: Setup Python version ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} + - name: Install dependencies run: | python -m pip install --upgrade pip python -m pip install pytest if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Test with pytest run: | - pytest \ No newline at end of file + ls . \ No newline at end of file From 9fe79ca18d0d224bb81261cdc0a9b270693bf9d7 Mon Sep 17 00:00:00 2001 From: Daniel Dias Date: Wed, 20 Nov 2024 15:57:48 -0300 Subject: [PATCH 05/19] adding unit tests to check interval analysis --- tests/data/intervalanalysis/domain.rddl | 46 ++++++++++++++++++++++ tests/data/intervalanalysis/instance.rddl | 21 ++++++++++ tests/test_intervals.py | 48 +++++++++++++++++++++++ 3 files changed, 115 insertions(+) create mode 100644 tests/data/intervalanalysis/domain.rddl create mode 100644 tests/data/intervalanalysis/instance.rddl create mode 100644 tests/test_intervals.py diff --git a/tests/data/intervalanalysis/domain.rddl b/tests/data/intervalanalysis/domain.rddl new file mode 100644 index 00000000..4d60d1be --- /dev/null +++ b/tests/data/intervalanalysis/domain.rddl @@ -0,0 +1,46 @@ +domain test_domain { + + requirements = { + concurrent, // different reservoirs are controlled independently + reward-deterministic, // this domain does not use a stochastic reward + intermediate-nodes, // this domain uses intermediate pvariable nodes + constrained-state // this domain uses state constraints + }; + + types { + someobject: object; + }; + + pvariables { + // Constants + LIMIT(someobject): { non-fluent, real, default = 1.0 }; + + // Intermediate fluents + intermfluent(someobject): { interm-fluent, real }; + + // State fluents + realstatefluent(someobject): { state-fluent, real, default = 0.0 }; + + // Action fluents + actionfluent(someobject): { action-fluent, real, default = 0.0 }; + }; + + cpfs { + intermfluent(?o) = realstatefluent(?o) + actionfluent(?o); + + realstatefluent'(?o) = intermfluent(?o); + }; + + reward = (sum_{?o : someobject} [ realstatefluent(?o) ]); + + action-preconditions { + forall_{?o : someobject} actionfluent(?o) <= LIMIT(?o); + forall_{?o : someobject} actionfluent(?o) >= -LIMIT(?o); + }; + + state-invariants { + forall_{?o : someobject} realstatefluent(?o) <= LIMIT(?o); + forall_{?o : someobject} realstatefluent(?o) >= -LIMIT(?o); + }; + +} \ No newline at end of file diff --git a/tests/data/intervalanalysis/instance.rddl b/tests/data/intervalanalysis/instance.rddl new file mode 100644 index 00000000..035e7fd0 --- /dev/null +++ b/tests/data/intervalanalysis/instance.rddl @@ -0,0 +1,21 @@ +non-fluents nf_test_domain { + domain = test_domain; + objects { + someobject : {o1}; + }; + non-fluents { + LIMIT(o1) = 1.0; + }; +} +instance inst_test_domain { + domain = test_domain; + non-fluents = nf_test_domain; + + init-state { + realstatefluent(o1) = 0.0; + }; + + max-nondef-actions = pos-inf; + horizon = 2; + discount = 1.0; +} \ No newline at end of file diff --git a/tests/test_intervals.py b/tests/test_intervals.py new file mode 100644 index 00000000..cfd6522c --- /dev/null +++ b/tests/test_intervals.py @@ -0,0 +1,48 @@ +import numpy as np + +import pyRDDLGym +from pyRDDLGym.core.intervals import RDDLIntervalAnalysis + +TEST_DOMAIN = './tests/data/intervalanalysis/domain.rddl' +TEST_INSTANCE = './tests/data/intervalanalysis/instance.rddl' + +def get_action_bounds(env, policy): + action_bounds = None + + if policy != 'random': + return action_bounds + + action_bounds = {} + for action, prange in env.model.action_ranges.items(): + lower, upper = env._bounds[action] + if prange == 'bool': + lower = np.full(np.shape(lower), fill_value=0, dtype=int) + upper = np.full(np.shape(upper), fill_value=1, dtype=int) + action_bounds[action] = (lower, upper) + + return action_bounds + +def perform_interval_analysis(domain, instance, policy): + env = pyRDDLGym.make(domain, instance, vectorized=True) + + action_bounds = get_action_bounds(env, policy) + + analysis = RDDLIntervalAnalysis(env.model) + bounds = analysis.bound(action_bounds=action_bounds, per_epoch=True) + + env.close() + + return bounds + + +def test_interval_analysis_simple_case(): + bounds = perform_interval_analysis(TEST_DOMAIN, TEST_INSTANCE, 'random') + + reward_lower, reward_upper = bounds['reward'] + np.testing.assert_array_equal(reward_lower, [0.0, -1.0]) + np.testing.assert_array_equal(reward_upper, [0.0, 1.0]) + + realstatefluent_lower, realstatefluent_upper = bounds['realstatefluent'] + + np.testing.assert_array_equal(realstatefluent_lower.flatten(), [-1.0, -2.0]) + np.testing.assert_array_equal(realstatefluent_upper.flatten(), [1.0, 2.0]) \ No newline at end of file From 7fb51212a031b92dc68c3384c7e783f34fd1087a Mon Sep 17 00:00:00 2001 From: Daniel Dias Date: Thu, 21 Nov 2024 16:21:35 -0300 Subject: [PATCH 06/19] update tests and setup --- setup.py | 2 +- tests/test_intervals.py | 83 ++++++++++++++++++++++++++++++++++------- 2 files changed, 70 insertions(+), 15 deletions(-) diff --git a/setup.py b/setup.py index 21153f80..93e0aba9 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ url="https://github.com/pyrddlgym-project/pyRDDLGym", packages=find_packages(), install_requires=['ply', 'pillow>=9.2.0', 'matplotlib>=3.5.0', 'numpy>=1.22,<2', 'gymnasium', 'pygame', 'termcolor'], - python_requires=">=3.8,<3.12", + python_requires=">=3.8,<3.13", package_data={'': ['*.cfg']}, include_package_data=True, classifiers=[ diff --git a/tests/test_intervals.py b/tests/test_intervals.py index cfd6522c..5b735744 100644 --- a/tests/test_intervals.py +++ b/tests/test_intervals.py @@ -1,17 +1,19 @@ +import os import numpy as np import pyRDDLGym from pyRDDLGym.core.intervals import RDDLIntervalAnalysis -TEST_DOMAIN = './tests/data/intervalanalysis/domain.rddl' -TEST_INSTANCE = './tests/data/intervalanalysis/instance.rddl' +ROOT_DIR = os.path.dirname(__file__) -def get_action_bounds(env, policy): - action_bounds = None - - if policy != 'random': - return action_bounds - +TEST_DOMAIN = f'{ROOT_DIR}/data/intervalanalysis/domain.rddl' +TEST_INSTANCE = f'{ROOT_DIR}/data/intervalanalysis/instance.rddl' + +################################################################################## +# Helper functions +################################################################################## + +def get_action_bounds_from_env(env): action_bounds = {} for action, prange in env.model.action_ranges.items(): lower, upper = env._bounds[action] @@ -22,21 +24,30 @@ def get_action_bounds(env, policy): return action_bounds -def perform_interval_analysis(domain, instance, policy): +def perform_interval_analysis(domain, instance, action_bounds = None, state_bounds = None): env = pyRDDLGym.make(domain, instance, vectorized=True) - action_bounds = get_action_bounds(env, policy) + if action_bounds is None: + action_bounds = get_action_bounds_from_env(env) analysis = RDDLIntervalAnalysis(env.model) - bounds = analysis.bound(action_bounds=action_bounds, per_epoch=True) + bounds = analysis.bound(action_bounds=action_bounds, state_bounds=state_bounds, per_epoch=True) env.close() return bounds -def test_interval_analysis_simple_case(): - bounds = perform_interval_analysis(TEST_DOMAIN, TEST_INSTANCE, 'random') +################################################################################## +# Test definitions +################################################################################## + +def test_simple_case(): + ''' Evaluate if the interval propagation works well to a simple use case, + with a real-valued state fluent and the reward function, + without setting up the action and state bounds. + ''' + bounds = perform_interval_analysis(TEST_DOMAIN, TEST_INSTANCE) reward_lower, reward_upper = bounds['reward'] np.testing.assert_array_equal(reward_lower, [0.0, -1.0]) @@ -45,4 +56,48 @@ def test_interval_analysis_simple_case(): realstatefluent_lower, realstatefluent_upper = bounds['realstatefluent'] np.testing.assert_array_equal(realstatefluent_lower.flatten(), [-1.0, -2.0]) - np.testing.assert_array_equal(realstatefluent_upper.flatten(), [1.0, 2.0]) \ No newline at end of file + np.testing.assert_array_equal(realstatefluent_upper.flatten(), [1.0, 2.0]) + +def test_action_bounds(): + ''' Evaluate if the interval propagation works well to a simple use case, + with a real-valued state fluent and the reward function, + setting up the action bounds. + ''' + action_bounds = { + 'actionfluent': ( np.array([ -0.5 ]), np.array([ 0.5 ]) ) + } + + bounds = perform_interval_analysis(TEST_DOMAIN, TEST_INSTANCE, action_bounds) + + reward_lower, reward_upper = bounds['reward'] + np.testing.assert_array_equal(reward_lower, [0.0, -0.5]) + np.testing.assert_array_equal(reward_upper, [0.0, 0.5]) + + realstatefluent_lower, realstatefluent_upper = bounds['realstatefluent'] + + np.testing.assert_array_equal(realstatefluent_lower.flatten(), [-0.5, -1.0]) + np.testing.assert_array_equal(realstatefluent_upper.flatten(), [0.5, 1.0]) + +def test_state_bounds(): + ''' Evaluate if the interval propagation works well to a simple use case, + with a real-valued state fluent and the reward function, + setting up the state bounds. + ''' + state_bounds = { + 'statefluent': ( np.array([ -0.5 ]), np.array([ 0.5 ]) ) + } + + action_bounds = { + 'actionfluent': ( np.array([ -0.1 ]), np.array([ 0.1 ]) ) + } + + bounds = perform_interval_analysis(TEST_DOMAIN, TEST_INSTANCE, action_bounds, state_bounds) + + reward_lower, reward_upper = bounds['reward'] + np.testing.assert_array_equal(reward_lower, [-0.5, -0.6]) + np.testing.assert_array_equal(reward_upper, [0.5, 0.6]) + + realstatefluent_lower, realstatefluent_upper = bounds['realstatefluent'] + + np.testing.assert_array_equal(realstatefluent_lower.flatten(), [-0.5, -1.0]) + np.testing.assert_array_equal(realstatefluent_upper.flatten(), [0.5, 1.0]) \ No newline at end of file From 5a4a4c30366b60bd16f8435d1d9b86138e40f888 Mon Sep 17 00:00:00 2001 From: Daniel Dias Date: Thu, 21 Nov 2024 16:31:39 -0300 Subject: [PATCH 07/19] fix tests --- tests/test_intervals.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/test_intervals.py b/tests/test_intervals.py index 5b735744..88fba5d3 100644 --- a/tests/test_intervals.py +++ b/tests/test_intervals.py @@ -84,7 +84,7 @@ def test_state_bounds(): setting up the state bounds. ''' state_bounds = { - 'statefluent': ( np.array([ -0.5 ]), np.array([ 0.5 ]) ) + 'realstatefluent': ( np.array([ -0.5 ]), np.array([ 0.5 ]) ) } action_bounds = { @@ -99,5 +99,7 @@ def test_state_bounds(): realstatefluent_lower, realstatefluent_upper = bounds['realstatefluent'] - np.testing.assert_array_equal(realstatefluent_lower.flatten(), [-0.5, -1.0]) - np.testing.assert_array_equal(realstatefluent_upper.flatten(), [0.5, 1.0]) \ No newline at end of file + np.testing.assert_array_equal(realstatefluent_lower.flatten(), [-0.6, -0.7]) + np.testing.assert_array_equal(realstatefluent_upper.flatten(), [0.6, 0.7]) + +test_state_bounds() \ No newline at end of file From 6854c4add0e3f31c446eb046c60f0036794c1851 Mon Sep 17 00:00:00 2001 From: Daniel Dias Date: Thu, 21 Nov 2024 16:32:19 -0300 Subject: [PATCH 08/19] fix test --- tests/test_intervals.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_intervals.py b/tests/test_intervals.py index 88fba5d3..44dbc60f 100644 --- a/tests/test_intervals.py +++ b/tests/test_intervals.py @@ -100,6 +100,4 @@ def test_state_bounds(): realstatefluent_lower, realstatefluent_upper = bounds['realstatefluent'] np.testing.assert_array_equal(realstatefluent_lower.flatten(), [-0.6, -0.7]) - np.testing.assert_array_equal(realstatefluent_upper.flatten(), [0.6, 0.7]) - -test_state_bounds() \ No newline at end of file + np.testing.assert_array_equal(realstatefluent_upper.flatten(), [0.6, 0.7]) \ No newline at end of file From 6e892d1a4144e82317255ff2a5c732777b856263 Mon Sep 17 00:00:00 2001 From: Daniel Dias Date: Thu, 21 Nov 2024 16:33:38 -0300 Subject: [PATCH 09/19] update model and tests --- tests/data/intervalanalysis/domain.rddl | 8 ++++---- tests/test_intervals.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/data/intervalanalysis/domain.rddl b/tests/data/intervalanalysis/domain.rddl index 4d60d1be..5d1a6bd3 100644 --- a/tests/data/intervalanalysis/domain.rddl +++ b/tests/data/intervalanalysis/domain.rddl @@ -22,11 +22,11 @@ domain test_domain { realstatefluent(someobject): { state-fluent, real, default = 0.0 }; // Action fluents - actionfluent(someobject): { action-fluent, real, default = 0.0 }; + realactionfluent(someobject): { action-fluent, real, default = 0.0 }; }; cpfs { - intermfluent(?o) = realstatefluent(?o) + actionfluent(?o); + intermfluent(?o) = realstatefluent(?o) + realactionfluent(?o); realstatefluent'(?o) = intermfluent(?o); }; @@ -34,8 +34,8 @@ domain test_domain { reward = (sum_{?o : someobject} [ realstatefluent(?o) ]); action-preconditions { - forall_{?o : someobject} actionfluent(?o) <= LIMIT(?o); - forall_{?o : someobject} actionfluent(?o) >= -LIMIT(?o); + forall_{?o : someobject} realactionfluent(?o) <= LIMIT(?o); + forall_{?o : someobject} realactionfluent(?o) >= -LIMIT(?o); }; state-invariants { diff --git a/tests/test_intervals.py b/tests/test_intervals.py index 44dbc60f..e66aeb49 100644 --- a/tests/test_intervals.py +++ b/tests/test_intervals.py @@ -64,7 +64,7 @@ def test_action_bounds(): setting up the action bounds. ''' action_bounds = { - 'actionfluent': ( np.array([ -0.5 ]), np.array([ 0.5 ]) ) + 'realactionfluent': ( np.array([ -0.5 ]), np.array([ 0.5 ]) ) } bounds = perform_interval_analysis(TEST_DOMAIN, TEST_INSTANCE, action_bounds) @@ -88,7 +88,7 @@ def test_state_bounds(): } action_bounds = { - 'actionfluent': ( np.array([ -0.1 ]), np.array([ 0.1 ]) ) + 'realactionfluent': ( np.array([ -0.1 ]), np.array([ 0.1 ]) ) } bounds = perform_interval_analysis(TEST_DOMAIN, TEST_INSTANCE, action_bounds, state_bounds) From 37cdfb8a759c41423b93418cc3b672628d2f031f Mon Sep 17 00:00:00 2001 From: Daniel Dias Date: Thu, 21 Nov 2024 22:22:42 -0300 Subject: [PATCH 10/19] fixing tests for interval propagation --- tests/data/intervalanalysis/domain.rddl | 9 ++ tests/test_intervals.py | 119 +++++++++++++++++++++++- 2 files changed, 124 insertions(+), 4 deletions(-) diff --git a/tests/data/intervalanalysis/domain.rddl b/tests/data/intervalanalysis/domain.rddl index 5d1a6bd3..e899132d 100644 --- a/tests/data/intervalanalysis/domain.rddl +++ b/tests/data/intervalanalysis/domain.rddl @@ -20,6 +20,10 @@ domain test_domain { // State fluents realstatefluent(someobject): { state-fluent, real, default = 0.0 }; + diracdeltastatefluent(someobject): { state-fluent, real, default = 0.0 }; + bernoullistatefluent(someobject): { state-fluent, bool, default = false }; + normalstatefluent(someobject): { state-fluent, real, default = 0.0 }; + weibullstatefluent(someobject): { state-fluent, real, default = 0.0 }; // Action fluents realactionfluent(someobject): { action-fluent, real, default = 0.0 }; @@ -28,6 +32,11 @@ domain test_domain { cpfs { intermfluent(?o) = realstatefluent(?o) + realactionfluent(?o); + diracdeltastatefluent'(?o) = DiracDelta(1.0); + bernoullistatefluent'(?o) = Bernoulli(0.5); + normalstatefluent'(?o) = Normal(0.0, 1.0); + weibullstatefluent'(?o) = Weibull(1.0, 5.0); + realstatefluent'(?o) = intermfluent(?o); }; diff --git a/tests/test_intervals.py b/tests/test_intervals.py index e66aeb49..7f8377df 100644 --- a/tests/test_intervals.py +++ b/tests/test_intervals.py @@ -2,7 +2,7 @@ import numpy as np import pyRDDLGym -from pyRDDLGym.core.intervals import RDDLIntervalAnalysis +from pyRDDLGym.core.intervals import RDDLIntervalAnalysis, IntervalAnalysisStrategy ROOT_DIR = os.path.dirname(__file__) @@ -24,13 +24,13 @@ def get_action_bounds_from_env(env): return action_bounds -def perform_interval_analysis(domain, instance, action_bounds = None, state_bounds = None): +def perform_interval_analysis(domain, instance, action_bounds = None, state_bounds = None, strategy = IntervalAnalysisStrategy.SUPPORT, percentiles = None): env = pyRDDLGym.make(domain, instance, vectorized=True) if action_bounds is None: action_bounds = get_action_bounds_from_env(env) - analysis = RDDLIntervalAnalysis(env.model) + analysis = RDDLIntervalAnalysis(env.model, strategy=strategy, percentiles=percentiles) bounds = analysis.bound(action_bounds=action_bounds, state_bounds=state_bounds, per_epoch=True) env.close() @@ -100,4 +100,115 @@ def test_state_bounds(): realstatefluent_lower, realstatefluent_upper = bounds['realstatefluent'] np.testing.assert_array_equal(realstatefluent_lower.flatten(), [-0.6, -0.7]) - np.testing.assert_array_equal(realstatefluent_upper.flatten(), [0.6, 0.7]) \ No newline at end of file + np.testing.assert_array_equal(realstatefluent_upper.flatten(), [0.6, 0.7]) + +def test_diracdelta_propagation(): + ''' Evaluate if the interval propagation works well to a state fluent + that has its next value sampled by a Dirac Delta distribution. + ''' + fluent_name = 'diracdeltastatefluent' + + ## Support strategy + bounds = perform_interval_analysis(TEST_DOMAIN, TEST_INSTANCE, strategy = IntervalAnalysisStrategy.SUPPORT) + + fluent_lower, fluent_upper = bounds[fluent_name] + np.testing.assert_array_equal(fluent_lower.flatten(), [1.0, 1.0]) + np.testing.assert_array_equal(fluent_upper.flatten(), [1.0, 1.0]) + + ## Mean strategy + bounds = perform_interval_analysis(TEST_DOMAIN, TEST_INSTANCE, strategy = IntervalAnalysisStrategy.MEAN) + + fluent_lower, fluent_upper = bounds[fluent_name] + np.testing.assert_array_equal(fluent_lower.flatten(), [1.0, 1.0]) + np.testing.assert_array_equal(fluent_upper.flatten(), [1.0, 1.0]) + + ## Percentiles strategy + bounds = perform_interval_analysis(TEST_DOMAIN, TEST_INSTANCE, strategy = IntervalAnalysisStrategy.PERCENTILE, percentiles=[0.1, 0.9]) + + fluent_lower, fluent_upper = bounds[fluent_name] + np.testing.assert_array_equal(fluent_lower.flatten(), [1.0, 1.0]) + np.testing.assert_array_equal(fluent_upper.flatten(), [1.0, 1.0]) + +def test_bernoulli_propagation(): + ''' Evaluate if the interval propagation works well to a state fluent + that has its next value sampled by a Bernoulli distribution. + ''' + + fluent_name = 'bernoullistatefluent' + + ## Support strategy + bounds = perform_interval_analysis(TEST_DOMAIN, TEST_INSTANCE, strategy = IntervalAnalysisStrategy.SUPPORT) + + fluent_lower, fluent_upper = bounds[fluent_name] + np.testing.assert_array_equal(fluent_lower.flatten(), [0.0, 0.0]) + np.testing.assert_array_equal(fluent_upper.flatten(), [1.0, 1.0]) + + ## Mean strategy + bounds = perform_interval_analysis(TEST_DOMAIN, TEST_INSTANCE, strategy = IntervalAnalysisStrategy.MEAN) + + fluent_lower, fluent_upper = bounds[fluent_name] + np.testing.assert_array_equal(fluent_lower.flatten(), [0.5, 0.5]) + np.testing.assert_array_equal(fluent_upper.flatten(), [0.5, 0.5]) + + ## Percentiles strategy + bounds = perform_interval_analysis(TEST_DOMAIN, TEST_INSTANCE, strategy = IntervalAnalysisStrategy.PERCENTILE, percentiles=[0.1, 0.9]) + + fluent_lower, fluent_upper = bounds[fluent_name] + np.testing.assert_array_equal(fluent_lower.flatten(), [0.0, 0.0]) + np.testing.assert_array_equal(fluent_upper.flatten(), [1.0, 1.0]) + +def test_normal_propagation(): + ''' Evaluate if the interval propagation works well to a state fluent + that has its next value sampled by a Normal distribution. + ''' + + fluent_name = 'normalstatefluent' + + ## Support strategy + bounds = perform_interval_analysis(TEST_DOMAIN, TEST_INSTANCE, strategy = IntervalAnalysisStrategy.SUPPORT) + + fluent_lower, fluent_upper = bounds[fluent_name] + np.testing.assert_array_equal(fluent_lower.flatten(), [-np.inf, -np.inf]) + np.testing.assert_array_equal(fluent_upper.flatten(), [np.inf, np.inf]) + + ## Mean strategy + bounds = perform_interval_analysis(TEST_DOMAIN, TEST_INSTANCE, strategy = IntervalAnalysisStrategy.MEAN) + + fluent_lower, fluent_upper = bounds[fluent_name] + np.testing.assert_array_equal(fluent_lower.flatten(), [0.0, 0.0]) + np.testing.assert_array_equal(fluent_upper.flatten(), [0.0, 0.0]) + + ## Percentiles strategy + bounds = perform_interval_analysis(TEST_DOMAIN, TEST_INSTANCE, strategy = IntervalAnalysisStrategy.PERCENTILE, percentiles=[0.1, 0.9]) + + fluent_lower, fluent_upper = bounds[fluent_name] + np.testing.assert_array_almost_equal(fluent_lower.flatten(), [-1.281552, -1.281552], decimal=5) + np.testing.assert_array_almost_equal(fluent_upper.flatten(), [1.281552, 1.281552], decimal=5) + +def test_weibull_propagation(): + ''' Evaluate if the interval propagation works well to a state fluent + that has its next value sampled by a Weibull distribution. + ''' + + fluent_name = 'weibullstatefluent' + + ## Support strategy + bounds = perform_interval_analysis(TEST_DOMAIN, TEST_INSTANCE, strategy = IntervalAnalysisStrategy.SUPPORT) + + fluent_lower, fluent_upper = bounds[fluent_name] + np.testing.assert_array_equal(fluent_lower.flatten(), [0.0, 0.0]) + np.testing.assert_array_equal(fluent_upper.flatten(), [np.inf, np.inf]) + + ## Mean strategy + bounds = perform_interval_analysis(TEST_DOMAIN, TEST_INSTANCE, strategy = IntervalAnalysisStrategy.MEAN) + + fluent_lower, fluent_upper = bounds[fluent_name] + np.testing.assert_array_equal(fluent_lower.flatten(), [5.0, 5.0]) + np.testing.assert_array_equal(fluent_upper.flatten(), [5.0, 5.0]) + + ## Percentiles strategy + bounds = perform_interval_analysis(TEST_DOMAIN, TEST_INSTANCE, strategy = IntervalAnalysisStrategy.PERCENTILE, percentiles=[0.1, 0.9]) + + fluent_lower, fluent_upper = bounds[fluent_name] # TODO: instead of using precalculated numbers, we could use other libs to evaluate this + np.testing.assert_array_almost_equal(fluent_lower.flatten(), [0.5268, 0.5268], decimal=5) + np.testing.assert_array_almost_equal(fluent_upper.flatten(), [11.51293, 11.51293], decimal=5) \ No newline at end of file From 809339c08bd961ef3963e6b8ce7c5098f0679f2f Mon Sep 17 00:00:00 2001 From: Daniel Dias Date: Thu, 21 Nov 2024 22:30:14 -0300 Subject: [PATCH 11/19] fix pytest on CI --- .github/workflows/unit-tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/unit-tests.yaml b/.github/workflows/unit-tests.yaml index d175251f..b98400f2 100644 --- a/.github/workflows/unit-tests.yaml +++ b/.github/workflows/unit-tests.yaml @@ -34,4 +34,4 @@ jobs: - name: Test with pytest run: | - ls . \ No newline at end of file + pytest \ No newline at end of file From b2ea7d9da7f7a5b610cec451a63914ea53246063 Mon Sep 17 00:00:00 2001 From: Daniel Dias Date: Thu, 21 Nov 2024 22:32:01 -0300 Subject: [PATCH 12/19] fixed another step for CI --- .github/workflows/unit-tests.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/unit-tests.yaml b/.github/workflows/unit-tests.yaml index b98400f2..3907d91b 100644 --- a/.github/workflows/unit-tests.yaml +++ b/.github/workflows/unit-tests.yaml @@ -32,6 +32,11 @@ jobs: python -m pip install pytest if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Install local pyRDDLgym + run: | + cd .. + pip install ./pyRDDLgym + - name: Test with pytest run: | pytest \ No newline at end of file From 7c0f459a03c630635784baa3a520d8f08967b8ba Mon Sep 17 00:00:00 2001 From: Daniel Dias Date: Thu, 21 Nov 2024 22:33:19 -0300 Subject: [PATCH 13/19] chore: update CI script --- .github/workflows/unit-tests.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/unit-tests.yaml b/.github/workflows/unit-tests.yaml index 3907d91b..2ba2a800 100644 --- a/.github/workflows/unit-tests.yaml +++ b/.github/workflows/unit-tests.yaml @@ -34,7 +34,6 @@ jobs: - name: Install local pyRDDLgym run: | - cd .. pip install ./pyRDDLgym - name: Test with pytest From 31732f1416723a8f9c63db709d8673222e1ea18b Mon Sep 17 00:00:00 2001 From: Daniel Dias Date: Thu, 21 Nov 2024 22:35:11 -0300 Subject: [PATCH 14/19] fix CI --- .github/workflows/unit-tests.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/unit-tests.yaml b/.github/workflows/unit-tests.yaml index 2ba2a800..1ae71443 100644 --- a/.github/workflows/unit-tests.yaml +++ b/.github/workflows/unit-tests.yaml @@ -32,9 +32,9 @@ jobs: python -m pip install pytest if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - - name: Install local pyRDDLgym + - name: Install local pyRDDLGym run: | - pip install ./pyRDDLgym + pip install ./pyRDDLGym - name: Test with pytest run: | From e6e32ccc89b58185a9c8504bef2e470302ae837d Mon Sep 17 00:00:00 2001 From: Daniel Dias Date: Thu, 21 Nov 2024 22:36:06 -0300 Subject: [PATCH 15/19] fix CI --- .github/workflows/unit-tests.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/unit-tests.yaml b/.github/workflows/unit-tests.yaml index 1ae71443..33fda543 100644 --- a/.github/workflows/unit-tests.yaml +++ b/.github/workflows/unit-tests.yaml @@ -34,6 +34,7 @@ jobs: - name: Install local pyRDDLGym run: | + cd .. pip install ./pyRDDLGym - name: Test with pytest From 3e2727fcf8532f5659f023df1d626cbfc5d79083 Mon Sep 17 00:00:00 2001 From: Daniel Dias Date: Thu, 21 Nov 2024 22:39:46 -0300 Subject: [PATCH 16/19] update CI --- .github/workflows/unit-tests.yaml | 8 +++----- requirements.test.txt | 2 ++ 2 files changed, 5 insertions(+), 5 deletions(-) create mode 100644 requirements.test.txt diff --git a/.github/workflows/unit-tests.yaml b/.github/workflows/unit-tests.yaml index 33fda543..712c0d6d 100644 --- a/.github/workflows/unit-tests.yaml +++ b/.github/workflows/unit-tests.yaml @@ -26,14 +26,12 @@ jobs: with: python-version: ${{ matrix.python-version }} - - name: Install dependencies + - name: Install dependencies and pyRDDLGym locally run: | python -m pip install --upgrade pip - python -m pip install pytest - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + pip install -r requirements.txt + pip install -r requirements.test.txt - - name: Install local pyRDDLGym - run: | cd .. pip install ./pyRDDLGym diff --git a/requirements.test.txt b/requirements.test.txt new file mode 100644 index 00000000..feb62d07 --- /dev/null +++ b/requirements.test.txt @@ -0,0 +1,2 @@ +pytest +scipy \ No newline at end of file From 26a449bb8bfd373d83ffc7e0d405f012f75e281d Mon Sep 17 00:00:00 2001 From: Daniel Dias Date: Thu, 21 Nov 2024 22:48:18 -0300 Subject: [PATCH 17/19] update tests --- pyRDDLGym/core/intervals.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pyRDDLGym/core/intervals.py b/pyRDDLGym/core/intervals.py index ccb330ba..83c41eeb 100644 --- a/pyRDDLGym/core/intervals.py +++ b/pyRDDLGym/core/intervals.py @@ -914,8 +914,8 @@ def _bound_random(self, expr, intervals): return self._bound_student(expr, intervals) elif name == 'Gumbel': return self._bound_gumbel(expr, intervals) - elif name == 'Laplace': - return self._bound_laplace(expr, intervals) + # elif name == 'Laplace': + # return self._bound_laplace(expr, intervals) elif name == 'Cauchy': return self._bound_cauchy(expr, intervals) elif name == 'Gompertz': @@ -982,8 +982,8 @@ def _bound_bernoulli(self, expr, intervals): lower = np.zeros(shape=np.shape(lp), dtype=np.int64) upper = np.ones(shape=np.shape(up), dtype=np.int64) - lower = self._mask_assign(lower, lp >= lower_percentile, 1) - upper = self._mask_assign(upper, up <= upper_percentile, 0) + lower = self._mask_assign(lower, lower_percentile > (1 - lp), 1) + upper = self._mask_assign(upper, upper_percentile <= (1 - up), 0) return (lower, upper) @@ -1007,8 +1007,8 @@ def _bound_normal(self, expr, intervals): # mean + std * normal_inverted_cdf(p) lower_percentile, upper_percentile = self.percentiles - lower = lm * np.sqrt(lv) * stats.norm.ppf(lower_percentile) - upper = um * np.sqrt(uv) * stats.norm.ppf(upper_percentile) + lower = lm + np.sqrt(lv) * stats.norm.ppf(lower_percentile) + upper = um + np.sqrt(uv) * stats.norm.ppf(upper_percentile) return (lower, upper) if self.strategy == IntervalAnalysisStrategy.MEAN: From 04c35ef2671ece7adfaa6b5229c97c924e95cad0 Mon Sep 17 00:00:00 2001 From: Daniel Dias Date: Thu, 21 Nov 2024 22:52:31 -0300 Subject: [PATCH 18/19] update interval analysis --- pyRDDLGym/core/intervals.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyRDDLGym/core/intervals.py b/pyRDDLGym/core/intervals.py index 83c41eeb..c9651110 100644 --- a/pyRDDLGym/core/intervals.py +++ b/pyRDDLGym/core/intervals.py @@ -914,6 +914,7 @@ def _bound_random(self, expr, intervals): return self._bound_student(expr, intervals) elif name == 'Gumbel': return self._bound_gumbel(expr, intervals) + # not implemented # elif name == 'Laplace': # return self._bound_laplace(expr, intervals) elif name == 'Cauchy': From 9aaa299f73d65921d1d86743b7643b7f5caccb0b Mon Sep 17 00:00:00 2001 From: Daniel Dias Date: Thu, 21 Nov 2024 22:59:21 -0300 Subject: [PATCH 19/19] update interval analysis to use scipy --- .github/workflows/unit-tests.yaml | 2 +- requirements.txt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/unit-tests.yaml b/.github/workflows/unit-tests.yaml index 712c0d6d..e143584e 100644 --- a/.github/workflows/unit-tests.yaml +++ b/.github/workflows/unit-tests.yaml @@ -29,8 +29,8 @@ jobs: - name: Install dependencies and pyRDDLGym locally run: | python -m pip install --upgrade pip + pip install pytest pip install -r requirements.txt - pip install -r requirements.test.txt cd .. pip install ./pyRDDLGym diff --git a/requirements.txt b/requirements.txt index 37aa58a6..41730220 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,5 @@ gymnasium numpy>=1.22 pygame ply +scipy termcolor \ No newline at end of file