From 6f42b16ee42f7c3e27c3afbc186b3c6ebca26dd8 Mon Sep 17 00:00:00 2001 From: voetberg Date: Tue, 19 Sep 2023 10:03:11 -0500 Subject: [PATCH 1/8] Added the ds store to ignore --- .gitignore | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index cedeadf..f6db003 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Byte-compiled / optimized / DLL files __pycache__/ +*.DS_Store *.py[cod] *$py.class @@ -160,7 +161,4 @@ cython_debug/ #.idea/ # Repo specific files (testing) -test/test_files/empty_config.yaml -test/test_files/test_config.yaml -test/test_files/test_saving/survey_2023_05_19_12_57_23_4222/run_config.yaml -test/test_files/test_saving/survey_2023_05_19_12_57_23_4698/survey_results.json +test/test_files/* From ed1986cb8e235a961a91f5adc6eae9f271afc6dc Mon Sep 17 00:00:00 2001 From: voetberg Date: Tue, 19 Sep 2023 10:04:13 -0500 Subject: [PATCH 2/8] draft of cummulitive survey and tests --- .../Survey/__init__.py | 4 + .../Survey/cummulative_survey.py | 101 ++++++++++++++++++ .../Survey/survey.py | 4 +- test/test_cummaltive_survey.py | 46 ++++++++ 4 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 telescope_positioning_simulation/Survey/cummulative_survey.py create mode 100644 test/test_cummaltive_survey.py diff --git a/telescope_positioning_simulation/Survey/__init__.py b/telescope_positioning_simulation/Survey/__init__.py index 37e9f7d..5c50307 100644 --- a/telescope_positioning_simulation/Survey/__init__.py +++ b/telescope_positioning_simulation/Survey/__init__.py @@ -2,3 +2,7 @@ from telescope_positioning_simulation.Survey.observation_variables import ( ObservationVariables, ) +from telescope_positioning_simulation.Survey.cummulative_survey import ( + UniformSurvey, + LowVisiblitySurvey, +) diff --git a/telescope_positioning_simulation/Survey/cummulative_survey.py b/telescope_positioning_simulation/Survey/cummulative_survey.py new file mode 100644 index 0000000..0c5f73e --- /dev/null +++ b/telescope_positioning_simulation/Survey/cummulative_survey.py @@ -0,0 +1,101 @@ +from telescope_positioning_simulation.Survey.survey import Survey +import numpy as np +import pandas as pd + + +class UniformSurvey(Survey): + """ + A child survey that instead of evaluating the schedule at every step, evaluates it for a full schedule. + This survey requires a threshold is reached for each observation before it starts recording any sort of reward. + + Args: + obseravtory_config (dict): Setup parameters for Survey.ObservationVariables, the telescope configuration, as read by IO.ReadConfig + survey_config (dict): Parameters for the survey, including the stopping conditions, the validity conditions, the variables to collect, as read by IO.ReadConfig + threshold (float): Threshold the survey must pass to have its quality counted towards the total reward + uniform (str): ["site", "quality"] - If measuring the uniformity of the number of times each site has been visited, or the uniformity of the quality of observations + + Examples: + >>> survey = Survey(observatory_config, survey_config) + action_generator = ActionGenerator() # Attributary function to produce time, location pairs + for step in range(10): + action_time, action_location = action_generator() + update_action = {"time":[action_time], "location":{"ra":[action_location["ra"]], "decl":[action_location["decl"]]}} + observation, reward, stop, log = survey.step(update_action) + + >>> survey = Survey(observatory_config, survey_config) + # Run without changing the location, only stepping time forward + survey_results = survey() + """ + + def __init__( + self, + observatory_config: dict, + survey_config: dict, + threshold: float = 1.0, + uniform: str = "site", + ) -> None: + super().__init__(observatory_config, survey_config) + self.all_steps = pd.DataFrame() + + self.threshold = threshold + reward_function = { + "site": self.site_reward, + "quality": self.quality_reward, + } + assert uniform in reward_function + + self.reward_function = reward_function[uniform] + + def site_reward(self): + counts = self.all_steps["action"].value_counts() + reward_scale = 1 / (len(self.all_steps) * np.var(counts)) + + current_steps = self.all_steps.copy() + # Replace reward if it doesn't make it pass the threshold + current_steps.loc[ + self.all_steps["action"].isin(counts.index[counts < self.threshold]), + "reward", + ] = 0 + reward_sum = current_steps.groupby(["mjd", "action"])["reward"].sum().sum() + + return reward_scale * reward_sum + + def quality_reward(self): + reward_scale = 1 / (len(self.all_steps)) * np.var(self.all_steps["reward"]) + + current_steps = self.all_steps.copy() + # Replace reward if it doesn't make it pass the threshold + current_steps.loc[current_steps["reward"] < self.threshold, "reward"] = 0 + reward_sum = current_steps["reward"].sum() + return reward_scale * reward_sum + + def reset(self): + self.all_steps = pd.DataFrame() + return super().reset() + + def _reward(self, *args, **kwargs): + if len(self.all_steps) != 0: + reward = self.reward_function() + reward = reward if not pd.isnull(reward) else 0 + + return reward + else: + return 0 + + def step(self, action: dict): + observation, reward, stop, log = super().step(action) + observation_pd = {key: observation[key].ravel() for key in observation.keys()} + + observation_pd = pd.DataFrame(observation_pd) + observation_pd["action"] = str(action["location"]) + str(action["band"]) + observation_pd["reward"] = reward + + self.all_steps = self.all_steps.append(observation_pd) + + reward = self._reward() + return observation, reward, stop, log + + +class LowVisiblitySurvey(Survey): + def __init__(self, observatory_config: dict, survey_config: dict) -> None: + super().__init__(observatory_config, survey_config) diff --git a/telescope_positioning_simulation/Survey/survey.py b/telescope_positioning_simulation/Survey/survey.py index 9e1c899..af484d0 100644 --- a/telescope_positioning_simulation/Survey/survey.py +++ b/telescope_positioning_simulation/Survey/survey.py @@ -139,7 +139,9 @@ def step(self, action: dict): Returns: Tuple : observation (dict, containing survey_config["variables"], vality, Time (in mjd)), reward (array), stop (array), log (dictionary) """ - print(action) + if "time" not in action: + action["time"] = np.array(self.time) + self.observator.update(**action) self.time = self.observator.time.mjd.mean() observation = self._observation_calculation() diff --git a/test/test_cummaltive_survey.py b/test/test_cummaltive_survey.py new file mode 100644 index 0000000..daf696d --- /dev/null +++ b/test/test_cummaltive_survey.py @@ -0,0 +1,46 @@ +import pytest + +from telescope_positioning_simulation.Survey import UniformSurvey, LowVisiblitySurvey +from telescope_positioning_simulation.IO import ReadConfig + +action = {"location": {"ra": [0], "decl": [0]}, "band": "g"} +action_2 = {"location": {"ra": [1], "decl": [1]}, "band": "g"} + + +def test_uniform_site(): + obs_config = ReadConfig(survey=False)() + obs_config["location"] = {"ra": [0], "decl": [0]} + uniform_survey = UniformSurvey( + observatory_config=obs_config, survey_config=ReadConfig(survey=True)() + ) + assert len(uniform_survey.all_steps) == 0 + assert uniform_survey._reward() == 0 + + uniform_survey.step(action) + assert uniform_survey._reward() == 0 + assert len(uniform_survey.all_steps) == 1 + + uniform_survey.step(action_2) + assert len(uniform_survey.all_steps) == 2 + assert uniform_survey._reward() == 0 + + +def test_uniform_quality(): + + obs_config = ReadConfig(survey=False)() + obs_config["location"] = {"ra": [0], "decl": [0]} + uniform_survey = UniformSurvey( + observatory_config=obs_config, + survey_config=ReadConfig(survey=True)(), + uniform="quality", + ) + assert len(uniform_survey.all_steps) == 0 + assert uniform_survey._reward() == 0 + + uniform_survey.step(action) + assert uniform_survey._reward() == 0 + assert len(uniform_survey.all_steps) == 1 + + uniform_survey.step(action_2) + assert len(uniform_survey.all_steps) == 2 + assert uniform_survey._reward() == 0 From 83bb7781b0e80abc9db8613ab68c0518a9bf956c Mon Sep 17 00:00:00 2001 From: voetberg Date: Tue, 19 Sep 2023 12:43:27 -0500 Subject: [PATCH 3/8] Made a parent class for cummalitive surveys --- .../Survey/cummulative_survey.py | 66 +++++++++++++------ 1 file changed, 46 insertions(+), 20 deletions(-) diff --git a/telescope_positioning_simulation/Survey/cummulative_survey.py b/telescope_positioning_simulation/Survey/cummulative_survey.py index 0c5f73e..305a9d4 100644 --- a/telescope_positioning_simulation/Survey/cummulative_survey.py +++ b/telescope_positioning_simulation/Survey/cummulative_survey.py @@ -3,7 +3,33 @@ import pandas as pd -class UniformSurvey(Survey): +class CummulativeSurvey(Survey): + def __init__(self, observatory_config: dict, survey_config: dict) -> None: + super().__init__(observatory_config, survey_config) + self.all_steps = pd.DataFrame() + + def reset(self): + self.all_steps = pd.DataFrame() + return super().reset() + + def _subclass_reward(self): + raise NotImplemented + + def step(self, action: dict): + observation, reward, stop, log = super().step(action) + observation_pd = {key: observation[key].ravel() for key in observation.keys()} + + observation_pd = pd.DataFrame(observation_pd) + observation_pd["action"] = str(action["location"]) + str(action["band"]) + observation_pd["reward"] = reward + + self.all_steps = self.all_steps.append(observation_pd) + + reward = self._subclass_reward() + return observation, reward, stop, log + + +class UniformSurvey(CummulativeSurvey): """ A child survey that instead of evaluating the schedule at every step, evaluates it for a full schedule. This survey requires a threshold is reached for each observation before it starts recording any sort of reward. @@ -35,7 +61,6 @@ def __init__( uniform: str = "site", ) -> None: super().__init__(observatory_config, survey_config) - self.all_steps = pd.DataFrame() self.threshold = threshold reward_function = { @@ -69,33 +94,34 @@ def quality_reward(self): reward_sum = current_steps["reward"].sum() return reward_scale * reward_sum - def reset(self): - self.all_steps = pd.DataFrame() - return super().reset() - - def _reward(self, *args, **kwargs): + def _subclass_reward(self, *args, **kwargs): if len(self.all_steps) != 0: reward = self.reward_function() - reward = reward if not pd.isnull(reward) else 0 + reward = reward if not (pd.isnull(reward) or reward == -np.inf) else 0 return reward else: return 0 - def step(self, action: dict): - observation, reward, stop, log = super().step(action) - observation_pd = {key: observation[key].ravel() for key in observation.keys()} - observation_pd = pd.DataFrame(observation_pd) - observation_pd["action"] = str(action["location"]) + str(action["band"]) - observation_pd["reward"] = reward +class LowVisiblitySurvey(CummulativeSurvey): + def __init__( + self, observatory_config: dict, survey_config: dict, required_sites: dict = {} + ) -> None: + super().__init__(observatory_config, survey_config) - self.all_steps = self.all_steps.append(observation_pd) + self.all_steps = pd.DataFrame() + self.required_sites = required_sites - reward = self._reward() - return observation, reward, stop, log + def _subclass_reward(self): + if len(self.all_steps) != 0: + reward_scale = 1 / len(self.all_steps) + weighted_term = self.weight * self.all_steps["reward"].sum() + number_of_interest_hit = "" + reward = reward_scale * (weighted_term + number_of_interest_hit) + reward = reward if not (pd.isnull(reward) or reward == -np.inf) else 0 -class LowVisiblitySurvey(Survey): - def __init__(self, observatory_config: dict, survey_config: dict) -> None: - super().__init__(observatory_config, survey_config) + return reward + else: + return 0 From 0846e87aacf8bb163a2ee4a9f5150c7d27e96a76 Mon Sep 17 00:00:00 2001 From: voetberg Date: Wed, 20 Sep 2023 08:35:27 -0500 Subject: [PATCH 4/8] Low visiblity survey and tests --- .../Survey/cummulative_survey.py | 57 +++++-- test/test_cummaltive_survey.py | 155 +++++++++++++++++- 2 files changed, 189 insertions(+), 23 deletions(-) diff --git a/telescope_positioning_simulation/Survey/cummulative_survey.py b/telescope_positioning_simulation/Survey/cummulative_survey.py index 305a9d4..7b588f6 100644 --- a/telescope_positioning_simulation/Survey/cummulative_survey.py +++ b/telescope_positioning_simulation/Survey/cummulative_survey.py @@ -1,6 +1,7 @@ from telescope_positioning_simulation.Survey.survey import Survey import numpy as np import pandas as pd +import json class CummulativeSurvey(Survey): @@ -20,7 +21,11 @@ def step(self, action: dict): observation_pd = {key: observation[key].ravel() for key in observation.keys()} observation_pd = pd.DataFrame(observation_pd) - observation_pd["action"] = str(action["location"]) + str(action["band"]) + observation_pd["action"] = str( + {"location": action["location"], "band": self.observator.band} + ) + observation_pd["band"] = self.observator.band + observation_pd["location"] = str(action["location"]) observation_pd["reward"] = reward self.all_steps = self.all_steps.append(observation_pd) @@ -39,18 +44,6 @@ class UniformSurvey(CummulativeSurvey): survey_config (dict): Parameters for the survey, including the stopping conditions, the validity conditions, the variables to collect, as read by IO.ReadConfig threshold (float): Threshold the survey must pass to have its quality counted towards the total reward uniform (str): ["site", "quality"] - If measuring the uniformity of the number of times each site has been visited, or the uniformity of the quality of observations - - Examples: - >>> survey = Survey(observatory_config, survey_config) - action_generator = ActionGenerator() # Attributary function to produce time, location pairs - for step in range(10): - action_time, action_location = action_generator() - update_action = {"time":[action_time], "location":{"ra":[action_location["ra"]], "decl":[action_location["decl"]]}} - observation, reward, stop, log = survey.step(update_action) - - >>> survey = Survey(observatory_config, survey_config) - # Run without changing the location, only stepping time forward - survey_results = survey() """ def __init__( @@ -81,7 +74,9 @@ def site_reward(self): self.all_steps["action"].isin(counts.index[counts < self.threshold]), "reward", ] = 0 - reward_sum = current_steps.groupby(["mjd", "action"])["reward"].sum().sum() + reward_sum = ( + current_steps.groupby(["mjd", "action", "band"])["reward"].sum().sum() + ) return reward_scale * reward_sum @@ -106,22 +101,50 @@ def _subclass_reward(self, *args, **kwargs): class LowVisiblitySurvey(CummulativeSurvey): def __init__( - self, observatory_config: dict, survey_config: dict, required_sites: dict = {} + self, + observatory_config: dict, + survey_config: dict, + required_sites: list = [], + other_site_weight: float = 0.6, + time_tolerance: float = 0.01388, ) -> None: super().__init__(observatory_config, survey_config) - self.all_steps = pd.DataFrame() self.required_sites = required_sites + self.time_tolerance = time_tolerance + self.weight = other_site_weight + + def sites_hit(self): + # TODO do this with sets and arrays instead of a loop + hit_counter = 0 + for site in self.required_sites: + + subset = self.all_steps.copy() + if "time" in site.keys(): + subset = subset[ + (subset["mjd"] < site["time"][0] + self.time_tolerance) + & (subset["mjd"] > site["time"][0] - self.time_tolerance) + ] + + if "band" in site.keys(): + subset = subset[subset["band"] == site["band"]] + + subset = subset[subset["location"] == str(site["location"])] + hit_counter += len(subset) + + return hit_counter def _subclass_reward(self): if len(self.all_steps) != 0: + reward_scale = 1 / len(self.all_steps) weighted_term = self.weight * self.all_steps["reward"].sum() - number_of_interest_hit = "" + number_of_interest_hit = self.sites_hit() reward = reward_scale * (weighted_term + number_of_interest_hit) reward = reward if not (pd.isnull(reward) or reward == -np.inf) else 0 return reward + else: return 0 diff --git a/test/test_cummaltive_survey.py b/test/test_cummaltive_survey.py index daf696d..fec83ed 100644 --- a/test/test_cummaltive_survey.py +++ b/test/test_cummaltive_survey.py @@ -10,37 +10,180 @@ def test_uniform_site(): obs_config = ReadConfig(survey=False)() obs_config["location"] = {"ra": [0], "decl": [0]} + obs_config["start_time"] = 59946 uniform_survey = UniformSurvey( observatory_config=obs_config, survey_config=ReadConfig(survey=True)() ) assert len(uniform_survey.all_steps) == 0 - assert uniform_survey._reward() == 0 + assert uniform_survey._subclass_reward() == 0 uniform_survey.step(action) - assert uniform_survey._reward() == 0 + assert uniform_survey._subclass_reward() == 0 assert len(uniform_survey.all_steps) == 1 uniform_survey.step(action_2) assert len(uniform_survey.all_steps) == 2 - assert uniform_survey._reward() == 0 + assert uniform_survey._subclass_reward() == 0 def test_uniform_quality(): obs_config = ReadConfig(survey=False)() obs_config["location"] = {"ra": [0], "decl": [0]} + obs_config["start_time"] = 59946 + uniform_survey = UniformSurvey( observatory_config=obs_config, survey_config=ReadConfig(survey=True)(), uniform="quality", ) assert len(uniform_survey.all_steps) == 0 - assert uniform_survey._reward() == 0 + assert uniform_survey._subclass_reward() == 0 uniform_survey.step(action) - assert uniform_survey._reward() == 0 + assert uniform_survey._subclass_reward() == 0 assert len(uniform_survey.all_steps) == 1 uniform_survey.step(action_2) assert len(uniform_survey.all_steps) == 2 - assert uniform_survey._reward() == 0 + assert uniform_survey._subclass_reward() == 0 + + +# Todo - schedule that passes the conditions in the defaults + + +def test_lowvis_hit_required_sites(): + required_sites = [ + {"location": {"ra": [ra], "decl": [decl]}} for ra, decl in zip([0, 10], [0, 10]) + ] + expected_reward = 1 + obs_config = ReadConfig(survey=False)() + obs_config["location"] = {"ra": [0], "decl": [0]} + survey_config = ReadConfig(survey=True)() + survey_config["invalid_penality"] = 0 + + survey = LowVisiblitySurvey( + observatory_config=obs_config, + survey_config=survey_config, + required_sites=required_sites, + other_site_weight=0, + ) + actions = [ + {"location": {"ra": [0], "decl": [0]}, "band": "g"}, + {"location": {"ra": [10], "decl": [10]}, "band": "g"}, + ] + for action in actions: + survey.step(action) + + print(survey.all_steps) + assert survey._subclass_reward() >= expected_reward + + +def test_lowvis_hit_required_sites_correct_time(): + required_sites = [ + {"time": [time], "location": {"ra": [ra], "decl": [decl]}} + for ra, decl, time in zip([0, 10], [0, 10], [59946.08, 59946.1]) + ] + + expected_reward = 1 + obs_config = ReadConfig(survey=False)() + obs_config["location"] = {"ra": [0], "decl": [0]} + survey_config = ReadConfig(survey=True)() + survey_config["invalid_penality"] = 0 + + survey = LowVisiblitySurvey( + observatory_config=obs_config, + survey_config=survey_config, + required_sites=required_sites, + other_site_weight=0, + ) + for action in required_sites: + survey.step(action) + + assert survey._subclass_reward() >= expected_reward + + +def test_lowvis_hit_required_sites_incorrect_time(): + required_sites = [ + {"time": [time], "location": {"ra": [ra], "decl": [decl]}} + for ra, decl, time in zip([0, 10], [0, 10], [59946.08, 59946.1]) + ] + + expected_reward = 1 + obs_config = ReadConfig(survey=False)() + obs_config["location"] = {"ra": [0], "decl": [0]} + survey_config = ReadConfig(survey=True)() + survey_config["invalid_penality"] = 0 + + survey = LowVisiblitySurvey( + observatory_config=obs_config, + survey_config=survey_config, + required_sites=required_sites, + other_site_weight=0, + ) + actions = [ + {"time": [time], "location": {"ra": [ra], "decl": [decl]}} + for ra, decl, time in zip([0, 10], [0, 10], [59948, 59948.1]) + ] + for action in actions: + survey.step(action) + + assert survey._subclass_reward() < expected_reward + + +def test_lowvis_hit_required_sites_incorrect_band(): + required_sites = [ + {"band": band, "location": {"ra": [ra], "decl": [decl]}} + for ra, decl, band in zip([0, 10], [0, 10], ["g", "g"]) + ] + + expected_reward = 1 + obs_config = ReadConfig(survey=False)() + obs_config["location"] = {"ra": [0], "decl": [0]} + survey_config = ReadConfig(survey=True)() + survey_config["invalid_penality"] = 0 + + survey = LowVisiblitySurvey( + observatory_config=obs_config, + survey_config=survey_config, + required_sites=required_sites, + other_site_weight=0, + ) + actions = [ + {"band": band, "location": {"ra": [ra], "decl": [decl]}} + for ra, decl, band in zip([0, 10], [0, 10], ["b", "v"]) + ] + + for action in actions: + survey.step(action) + + assert survey._subclass_reward() < expected_reward + + +def test_lowvis_incorrect_sites(): + required_sites = [ + {"location": {"ra": [ra], "decl": [decl]}} + for ra, decl in zip([15, 20], [10, 20]) + ] + + expected_reward = 1 + obs_config = ReadConfig(survey=False)() + obs_config["location"] = {"ra": [0], "decl": [0]} + + survey_config = ReadConfig(survey=True)() + survey_config["invalid_penality"] = 0 + + survey = LowVisiblitySurvey( + observatory_config=obs_config, + survey_config=survey_config, + required_sites=required_sites, + other_site_weight=0, + ) + actions = [ + {"location": {"ra": [ra], "decl": [decl]}} for ra, decl in zip([0, 10], [0, 10]) + ] + + for action in actions: + survey.step(action) + + assert survey._subclass_reward() < expected_reward From 5f54a324408bdfe520bdff7cb4173578c1c90810 Mon Sep 17 00:00:00 2001 From: voetberg Date: Thu, 21 Sep 2023 11:19:21 -0500 Subject: [PATCH 5/8] Basic docstring additions --- .../Survey/cummulative_survey.py | 31 ++++++++++++++++--- .../Survey/survey.py | 6 ++++ test/test_cummaltive_survey.py | 22 ++++++------- 3 files changed, 43 insertions(+), 16 deletions(-) diff --git a/telescope_positioning_simulation/Survey/cummulative_survey.py b/telescope_positioning_simulation/Survey/cummulative_survey.py index 7b588f6..953c425 100644 --- a/telescope_positioning_simulation/Survey/cummulative_survey.py +++ b/telescope_positioning_simulation/Survey/cummulative_survey.py @@ -5,18 +5,39 @@ class CummulativeSurvey(Survey): - def __init__(self, observatory_config: dict, survey_config: dict) -> None: + def __init__( + self, observatory_config: dict, survey_config: dict, *args, **kwargs + ) -> None: + """ + Abstract class for a survey that evaluated across multiple steps + + Args: + observatory_config (dict): _description_ + survey_config (dict): _description_ + """ super().__init__(observatory_config, survey_config) self.all_steps = pd.DataFrame() def reset(self): + """Return the survey to its initial condition, wipe out all the original steps""" self.all_steps = pd.DataFrame() return super().reset() - def _subclass_reward(self): + def cummulative_reward(self): + """Reward that uses the 'all_steps' class parameter.""" raise NotImplemented def step(self, action: dict): + """ + Move the observator forward with one action and add the reward and stop condition to the returned observation. + Reward is defined with 'cummulative_reward' + + Args: + action (dict): Dictionary containing "time" (array in units Mean Julian Date) "location"(dict with ra, decl, in degrees as arrays) (optional), "band" (str of the represention of the optical filter) (optional) + + Returns: + Tuple : observation (dict, containing survey_config["variables"], vality, Time (in mjd)), reward (array), stop (array), log (dictionary) + """ observation, reward, stop, log = super().step(action) observation_pd = {key: observation[key].ravel() for key in observation.keys()} @@ -30,7 +51,7 @@ def step(self, action: dict): self.all_steps = self.all_steps.append(observation_pd) - reward = self._subclass_reward() + reward = self.cummulative_reward() return observation, reward, stop, log @@ -89,7 +110,7 @@ def quality_reward(self): reward_sum = current_steps["reward"].sum() return reward_scale * reward_sum - def _subclass_reward(self, *args, **kwargs): + def cummulative_reward(self, *args, **kwargs): if len(self.all_steps) != 0: reward = self.reward_function() reward = reward if not (pd.isnull(reward) or reward == -np.inf) else 0 @@ -134,7 +155,7 @@ def sites_hit(self): return hit_counter - def _subclass_reward(self): + def cummulative_reward(self): if len(self.all_steps) != 0: reward_scale = 1 / len(self.all_steps) diff --git a/telescope_positioning_simulation/Survey/survey.py b/telescope_positioning_simulation/Survey/survey.py index af484d0..8512b82 100644 --- a/telescope_positioning_simulation/Survey/survey.py +++ b/telescope_positioning_simulation/Survey/survey.py @@ -166,6 +166,12 @@ def _observation_calculation(self): return observation def __call__(self): + """ + Run the survey with the initial location until the stopping condition is met, return the completed survey + + Returns: + dict: Evaluated survey in the form of time:{"variable_name":[variable_value]} + """ stop = False results = {} while not stop: diff --git a/test/test_cummaltive_survey.py b/test/test_cummaltive_survey.py index fec83ed..3aa31d6 100644 --- a/test/test_cummaltive_survey.py +++ b/test/test_cummaltive_survey.py @@ -15,15 +15,15 @@ def test_uniform_site(): observatory_config=obs_config, survey_config=ReadConfig(survey=True)() ) assert len(uniform_survey.all_steps) == 0 - assert uniform_survey._subclass_reward() == 0 + assert uniform_survey.cummulative_reward() == 0 uniform_survey.step(action) - assert uniform_survey._subclass_reward() == 0 + assert uniform_survey.cummulative_reward() == 0 assert len(uniform_survey.all_steps) == 1 uniform_survey.step(action_2) assert len(uniform_survey.all_steps) == 2 - assert uniform_survey._subclass_reward() == 0 + assert uniform_survey.cummulative_reward() == 0 def test_uniform_quality(): @@ -38,15 +38,15 @@ def test_uniform_quality(): uniform="quality", ) assert len(uniform_survey.all_steps) == 0 - assert uniform_survey._subclass_reward() == 0 + assert uniform_survey.cummulative_reward() == 0 uniform_survey.step(action) - assert uniform_survey._subclass_reward() == 0 + assert uniform_survey.cummulative_reward() == 0 assert len(uniform_survey.all_steps) == 1 uniform_survey.step(action_2) assert len(uniform_survey.all_steps) == 2 - assert uniform_survey._subclass_reward() == 0 + assert uniform_survey.cummulative_reward() == 0 # Todo - schedule that passes the conditions in the defaults @@ -76,7 +76,7 @@ def test_lowvis_hit_required_sites(): survey.step(action) print(survey.all_steps) - assert survey._subclass_reward() >= expected_reward + assert survey.cummulative_reward() >= expected_reward def test_lowvis_hit_required_sites_correct_time(): @@ -100,7 +100,7 @@ def test_lowvis_hit_required_sites_correct_time(): for action in required_sites: survey.step(action) - assert survey._subclass_reward() >= expected_reward + assert survey.cummulative_reward() >= expected_reward def test_lowvis_hit_required_sites_incorrect_time(): @@ -128,7 +128,7 @@ def test_lowvis_hit_required_sites_incorrect_time(): for action in actions: survey.step(action) - assert survey._subclass_reward() < expected_reward + assert survey.cummulative_reward() < expected_reward def test_lowvis_hit_required_sites_incorrect_band(): @@ -157,7 +157,7 @@ def test_lowvis_hit_required_sites_incorrect_band(): for action in actions: survey.step(action) - assert survey._subclass_reward() < expected_reward + assert survey.cummulative_reward() < expected_reward def test_lowvis_incorrect_sites(): @@ -186,4 +186,4 @@ def test_lowvis_incorrect_sites(): for action in actions: survey.step(action) - assert survey._subclass_reward() < expected_reward + assert survey.cummulative_reward() < expected_reward From ccf868f116cd1398fec3114dec38392c59d9574d Mon Sep 17 00:00:00 2001 From: voetberg Date: Thu, 21 Sep 2023 11:38:16 -0500 Subject: [PATCH 6/8] Docs for low visiblity survey --- .../Survey/cummulative_survey.py | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/telescope_positioning_simulation/Survey/cummulative_survey.py b/telescope_positioning_simulation/Survey/cummulative_survey.py index 953c425..3a88f48 100644 --- a/telescope_positioning_simulation/Survey/cummulative_survey.py +++ b/telescope_positioning_simulation/Survey/cummulative_survey.py @@ -129,6 +129,16 @@ def __init__( other_site_weight: float = 0.6, time_tolerance: float = 0.01388, ) -> None: + """ + Survey that evaluates how many times a "required site" has been visited, and defines the reward of a site based on this. + + Args: + obseravtory_config (dict): Setup parameters for Survey.ObservationVariables, the telescope configuration, as read by IO.ReadConfig + survey_config (dict): Parameters for the survey, including the stopping conditions, the validity conditions, the variables to collect, as read by IO.ReadConfig + required_sites (list, optional): Sites that must be visited to achieve reward. Same format as an action. Defaults to []. + other_site_weight (float, optional): Weighting factor applied to reward gained from visiting sites not in the "required_sites" list. Defaults to 0.6. + time_tolerance (float, optional): Tolerance given to a required time. Defaults to 0.01388 (seconds, 20 minutes). + """ super().__init__(observatory_config, survey_config) self.required_sites = required_sites @@ -136,6 +146,12 @@ def __init__( self.weight = other_site_weight def sites_hit(self): + """Count the number of times a required site was visited. Does not follow a thresholding rule. + + Returns: + int: times the requires sites were visited. + """ + # TODO do this with sets and arrays instead of a loop hit_counter = 0 for site in self.required_sites: @@ -156,6 +172,13 @@ def sites_hit(self): return hit_counter def cummulative_reward(self): + """ + Reward for all visited sites as defined as + $$ R_{s_{n}} = \frac{1}{||T||} (||\\{s_i: s_i \\in S_{interest}\\}|| + \\lambda \\Sigma^{t_{n}}_{t=0} T_{eff_t}(s_t)) $$ + + Returns: + float: reward calculted as a result of all current sites visited in the schedule + """ if len(self.all_steps) != 0: reward_scale = 1 / len(self.all_steps) @@ -168,4 +191,4 @@ def cummulative_reward(self): return reward else: - return 0 + return 0.0 From 56dfea46411a3be6265e589749df0fc00acf148b Mon Sep 17 00:00:00 2001 From: voetberg Date: Thu, 21 Sep 2023 11:45:55 -0500 Subject: [PATCH 7/8] thresholded survey docstrings --- .../Survey/cummulative_survey.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/telescope_positioning_simulation/Survey/cummulative_survey.py b/telescope_positioning_simulation/Survey/cummulative_survey.py index 3a88f48..726445b 100644 --- a/telescope_positioning_simulation/Survey/cummulative_survey.py +++ b/telescope_positioning_simulation/Survey/cummulative_survey.py @@ -86,6 +86,15 @@ def __init__( self.reward_function = reward_function[uniform] def site_reward(self): + """ + Calculate the reward for all sites, assuming it is required for a threshold on the number of times each site is visited. + Follows the equation: + + $$R_{S_{n}} = \frac{1}{||T||* Var(\\{||(s_i)||: s_i \\in S_n\\})}* \\Sigma^{S_{n}}_{i=0} \begin{cases} \\Sigma^{t_{n}}_{j=0} \tau_{eff}(s_{i,j}) & ||s_i|| \\geq N \\ 0 & ||s_i|| < N \\ \\end{cases}$$ + + Returns: + float: reward based on if number of sites a site was visited reached a threshold + """ counts = self.all_steps["action"].value_counts() reward_scale = 1 / (len(self.all_steps) * np.var(counts)) @@ -102,6 +111,14 @@ def site_reward(self): return reward_scale * reward_sum def quality_reward(self): + """Cummulitive reward based on if a per site reward threshold is reached. + Defined as: + + $$R_{S_{n}} = \frac{1}{||T||* Var(\\{\tau_{eff}(s_i): s_i \\in S_n)\\}}* \\Sigma^{t_{n}}_{i=0} \begin{cases} \tau_{eff}(s_i) & T_{eff}(s_i) \\geq \theta \\ 0 & \tau_{eff}(s_i) < \theta \\ \\end{cases}$$ + + Returns: + float: reward based on if a threshold in reward per site is reached. + """ reward_scale = 1 / (len(self.all_steps)) * np.var(self.all_steps["reward"]) current_steps = self.all_steps.copy() @@ -111,6 +128,12 @@ def quality_reward(self): return reward_scale * reward_sum def cummulative_reward(self, *args, **kwargs): + """ + Quality thresholded or visit thresholded reward + + Returns: + float: all sites reward + """ if len(self.all_steps) != 0: reward = self.reward_function() reward = reward if not (pd.isnull(reward) or reward == -np.inf) else 0 From e277f9cbef006a70baacc21b82262fdb5e6d50cc Mon Sep 17 00:00:00 2001 From: voetberg Date: Thu, 21 Sep 2023 11:49:07 -0500 Subject: [PATCH 8/8] Updated docs to autodoc --- docs/source/cummulative_survey.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 docs/source/cummulative_survey.rst diff --git a/docs/source/cummulative_survey.rst b/docs/source/cummulative_survey.rst new file mode 100644 index 0000000..08a4ccb --- /dev/null +++ b/docs/source/cummulative_survey.rst @@ -0,0 +1,10 @@ +Cummulative Surveys +======= +.. autoclass:: telescope_positioning_simulation.Survey.CummulativeSurvey + :members: + +.. autoclass:: telescope_positioning_simulation.Survey.UniformSurvey + :members: + +.. autoclass:: telescope_positioning_simulation.Survey.LowVisiblitySurvey + :members: \ No newline at end of file