Repository: ray-project/ray

Issue:
[tune] `tune.function` does not work with PTB scheduler 
<!--
General questions should be asked on the mailing list ray-dev@googlegroups.com.
Questions about how to use Ray should be asked on
[StackOverflow](https://stackoverflow.com/questions/tagged/ray).

Before submitting an issue, please fill out the following form.
-->

### System information
- **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**:macOS Moave 10.14.6
- **Ray installed from (source or binary)**: binary
- **Ray version**:0.73
- **Python version**:3.6
- **Exact command to reproduce**:

I simply took the `pbt_example.py` from the example folder and added a `tune.function` object in the config of `tune.run`:

```python
"""examples/pbt_example.py"""
from ray.tune import function

# ...

def some_function():  # <== added this function
    return 42

run(
    PBTBenchmarkExample,
    name="pbt_test",
    scheduler=pbt,
    reuse_actors=True,
    verbose=False,
    **{
        "stop": {
            "training_iteration": 2000,
        },
        "num_samples": 4,
        "config": {
            "lr": 0.0001,
            # note: this parameter is perturbed but has no effect on
            # the model training in this example
            "some_other_factor": 1,
            "some_function": function(some_function)  # <== added this line
        },
    })
```

### Describe the problem
Hi,
it seems that there is a problem in dumping the function as json? When you execute the code you will also get the known error `AttributeError: 'NoneType' object has no attribute 'get_global_worker'` as discussed in #5042.

Both errors do not occur when I remove the scheduler from the `tune.run` call.

Would be great if you could look into this.


## Source code / logs
```
2019-08-30 16:46:30,213	INFO pbt.py:82 -- [explore] perturbed config from {'lr': 0.0001, 'some_other_factor': 1, 'some_function': tune.function(<function some_function at 0x11fd31ae8>)} -> {'lr': 0.00012, 'some_other_factor': 2, 'some_function': tune.function(<function some_function at 0x11fd31ae8>)}
2019-08-30 16:46:30,213	INFO pbt.py:304 -- [exploit] transferring weights from trial PBTBenchmarkExample_3 (score 24.859665593890774) -> PBTBenchmarkExample_2 (score 1.1905894444680376)
2019-08-30 16:46:30,213	ERROR trial_runner.py:550 -- Error processing event.
Traceback (most recent call last):
  File "/Users/XXXX/anaconda2/envs/dev-p36/lib/python3.6/site-packages/ray/tune/trial_runner.py", line 520, in _process_trial
    self, trial, result)
  File "/Users/XXXX/anaconda2/envs/dev-p36/lib/python3.6/site-packages/ray/tune/schedulers/pbt.py", line 243, in on_trial_result
    self._exploit(trial_runner.trial_executor, trial, trial_to_clone)
  File "/Users/XXXX/anaconda2/envs/dev-p36/lib/python3.6/site-packages/ray/tune/schedulers/pbt.py", line 308, in _exploit
    trial_to_clone, new_config)
  File "/Users/XXXX/anaconda2/envs/dev-p36/lib/python3.6/site-packages/ray/tune/schedulers/pbt.py", line 277, in _log_config_on_step
    f.write(json.dumps(policy) + "\n")
  File "/Users/XXXX/anaconda2/envs/dev-p36/lib/python3.6/json/__init__.py", line 231, in dumps
    return _default_encoder.encode(obj)
  File "/Users/XXXX/anaconda2/envs/dev-p36/lib/python3.6/json/encoder.py", line 199, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/Users/XXXX/anaconda2/envs/dev-p36/lib/python3.6/json/encoder.py", line 257, in iterencode
    return _iterencode(o, 0)
  File "/Users/XXXX/anaconda2/envs/dev-p36/lib/python3.6/json/encoder.py", line 180, in default
    o.__class__.__name__)
TypeError: Object of type 'function' is not JSON serializable
```



Discussion:

Resolving patch:
diff --git a/python/ray/tune/schedulers/pbt.py b/python/ray/tune/schedulers/pbt.py
--- a/python/ray/tune/schedulers/pbt.py
+++ b/python/ray/tune/schedulers/pbt.py
@@ -13,6 +13,7 @@
 
 from ray.tune.error import TuneError
 from ray.tune.result import TRAINING_ITERATION
+from ray.tune.logger import _SafeFallbackEncoder
 from ray.tune.schedulers import FIFOScheduler, TrialScheduler
 from ray.tune.suggest.variant_generator import format_vars
 from ray.tune.trial import Trial, Checkpoint
@@ -276,13 +277,13 @@ def _log_config_on_step(self, trial_state, new_state, trial,
         ]
         # Log to global file.
         with open(os.path.join(trial.local_dir, "pbt_global.txt"), "a+") as f:
-            f.write(json.dumps(policy) + "\n")
+            print(json.dumps(policy, cls=_SafeFallbackEncoder), file=f)
         # Overwrite state in target trial from trial_to_clone.
         if os.path.exists(trial_to_clone_path):
             shutil.copyfile(trial_to_clone_path, trial_path)
         # Log new exploit in target trial log.
         with open(trial_path, "a+") as f:
-            f.write(json.dumps(policy) + "\n")
+            f.write(json.dumps(policy, cls=_SafeFallbackEncoder) + "\n")
 
     def _exploit(self, trial_executor, trial, trial_to_clone):
         """Transfers perturbed state from trial_to_clone -> trial.


Test patch: