Habitat-API Configuration
==============================

Habitat-API uses [Yacs configuration system](https://github.com/rbgirshick/yacs) 
with the paradigm of `your code + a YACS config for experiment E (+
external dependencies + hardware + other nuisance terms ...) =
reproducible experiment E`. Yacs advantages:
- Checks for type consistency.
- All parameters and default values are searchable in the code.
- A parameter doesn't need to be set always as each parameter has a 
    default value.
- Ability to freeze config to prevent unintended changes.

## Config usage
An example of how to merge default config with 2 others configs and overwrite 
one parameter that could come from the command line:
```
    merged_config = get_config(
        config_paths=["configs/tasks/pointnav.yaml", 
            "configs/dataset/val.yaml"],
        opts=["ENVIRONMENT.MAX_EPISODE_STEPS", steps_limit]
    )

```

## Config structure
Below is the structure of config used for Habitat:
- Environment
- Task
    - Sensors
    - Measurements
- Simulator
    - Agent
        - Sensors
- Dataset

We use node names (e.g. `SENSORS: ['RGB_SENSOR', 'DEPTH_SENSOR']`) instead of list 
of config nodes (e.g. `SENSORS: [{TYPE = "HabitatSimDepthSensor", 
MIN_DEPTH = 0}, ...]`) to declare the Sensors attached to an Agent or Measures 
enabled for the Task . With this approach, it's still easy to overwrite a 
particular sensor parameter in yaml file without redefining the whole sensor 
config. 

## Extending the config
Example of how to extend a config outside of `habtiat-api` repository.
First, we create a config extending the default config in the code and re-use 
`habitat.get_config()`:
```
import habitat
import argparse
from typing import List, Optional, Union

_C = habitat.get_config()
_C.defrost()
# Add new parameters to the config
_C.TASK.EPISODE_INFO = habitat.Config()
_C.TASK.EPISODE_INFO.TYPE = "EpisodeInfo"
_C.TASK.EPISODE_INFO.VALUE = 5
_C.TASK.MEASUREMENTS.append("EPISODE_INFO")

# New function returning extended Habitat config that should be used instead
# of habitat.get_config()
def my_get_config(
        config_paths: Optional[Union[List[str], str]] = None,
        opts: Optional[list] = None,
) -> habitat.Config:
    CONFIG_FILE_SEPARATOR = ","
    config = _C.clone()
    if config_paths:
        if isinstance(config_paths, str):
            if CONFIG_FILE_SEPARATOR in config_paths:
                config_paths = config_paths.split(CONFIG_FILE_SEPARATOR)
            else:
                config_paths = [config_paths]

        for config_path in config_paths:
            config.merge_from_file(config_path)

    if opts:
        config.merge_from_list(opts)

    config.freeze()
    return config

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--task-config",
        type=str,
        default="configs/tasks/pointnav.yaml,"
                "configs/datasets/pointnav/habitat_test.yaml",
    )
    parser.add_argument(
        "opts",
        default=None,
        nargs=argparse.REMAINDER,
        help="Modify config options from command line",
    )
    args = parser.parse_args()
    config = my_get_config(config_paths=args.task_config, opts=args.opts)
    env = habitat.Env(config)

``` 