Skip to content

Commit

Permalink
Merge pull request #22 from openEDI/docker_container
Browse files Browse the repository at this point in the history
Add docker container and use build directory
  • Loading branch information
josephmckinsey authored Sep 16, 2022
2 parents 8f81a4f + 036d15c commit 58423a3
Show file tree
Hide file tree
Showing 18 changed files with 418 additions and 56 deletions.
52 changes: 52 additions & 0 deletions .github/workflows/docker-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: DockerTest

on: [push]
jobs:
docker:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
python-version: ['3.10']
steps:
- uses: actions/checkout@v2
with:
submodules: true
token: ${{ secrets.CI_TOKEN }}
- name: build docker container
shell: bash
run: |
echo "$SSH_KEY" > gadal_docker_key
docker build --secret id=gadal_github_key,src=gadal_docker_key --progress=plain -t gadal-example:0.0.0 .
env:
SSH_KEY: ${{secrets.SGIDAL_CLONE_KEY}}
DOCKER_BUILDKIT: '1'
- name: run docker continaer
shell: bash
run: |
mkdir outputs_build
docker volume create --name gadal_output --opt type=none --opt device=$(pwd)/outputs_build --opt o=bind
docker run --rm --mount source=gadal_output,target=/simulation/outputs gadal-example:0.0.0
- name: Set up Python ${{ matrix.python-version }}
uses: conda-incubator/setup-miniconda@v2
with:
auto-update-conda: true
python-version: ${{ matrix.python-version }}
- name: Run plots
shell: bash -l {0}
run: |
pip install matplotlib pyarrow numpy matplotlib pandas
eval `ssh-agent -s`
ssh-add - <<< '${{ secrets.SGIDAL_CLONE_KEY }}'
pip install git+ssh://[email protected]/openEDI/[email protected]
python post_analysis.py outputs_build
- name: Archive logs
uses: actions/upload-artifact@v2
if: always()
with:
name: docker_logs
path: |
outputs_build/*.log
*.png
15 changes: 4 additions & 11 deletions .github/workflows/test-api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,15 @@ jobs:
- name: Install python dependencies
shell: bash -l {0}
run: |
pip install helics==3.1.2.post7
pip install helics-apps==3.1.0
pip install git+https://github.com/GMLC-TDC/helics-cli.git@main
pip install pydantic
pip install pyarrow
pip install scipy matplotlib numpy pandas
pip install 'OpenDSSDirect.py[extras]'
pip install boto3
pip install -r requirements.txt
eval `ssh-agent -s`
ssh-add - <<< '${{ secrets.SGIDAL_CLONE_KEY }}'
pip install git+ssh://[email protected]/openEDI/GADAL.git@v0.2.1
pip install git+ssh://[email protected]/openEDI/[email protected].2
- name: Run example
shell: bash -l {0}
run: |
python test_full_systems.py
helics run --path=test_system_runner.json
helics run --path=build/test_system_runner.json
python post_analysis.py
- name: Archive logs
uses: actions/upload-artifact@v2
Expand All @@ -50,4 +43,4 @@ jobs:
name: test_logs
path: |
*.png
*.log
build/*.log
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ sensors
test_system_runner.json
recorder_*
local_feeder
build
1 change: 1 addition & 0 deletions AWSFeeder/FeederSimulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class FeederConfig(BaseModel):
start_date: str
increment_value: int # increment in seconds
number_of_timesteps: int
topology_output: str = "topology.json"


class FeederSimulator(object):
Expand Down
2 changes: 1 addition & 1 deletion AWSFeeder/sender_cosim.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ def get_phase(name):

logger.info("Sending topology and saving to topology.json")
pub_topology.publish(topology.json())
with open("topology.json", "w") as f:
with open(config.topology_output, "w") as f:
f.write(topology.json())

snapshot_run(sim)
Expand Down
26 changes: 26 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
FROM python:3.10.6-slim-bullseye
#USER root
RUN apt-get update && apt-get install -y git ssh

RUN mkdir -p /root/.ssh
ENV GIT_SSH_COMMAND="ssh -i /run/secrets/gadal_github_key"
RUN ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts
RUN --mount=type=secret,id=gadal_github_key pip install git+ssh://[email protected]/openEDI/[email protected]

WORKDIR /simulation

COPY test_full_systems.py .
COPY docker_system.json .
COPY AWSFeeder AWSFeeder
COPY LocalFeeder LocalFeeder
COPY README.md .
COPY measuring_federate measuring_federate
COPY wls_federate wls_federate
COPY recorder recorder

RUN mkdir -p outputs build
RUN python test_full_systems.py --system docker_system.json

COPY requirements.txt .
RUN pip install -r requirements.txt
ENTRYPOINT ["helics", "run", "--path=build/test_system_runner.json"]
1 change: 1 addition & 0 deletions LocalFeeder/FeederSimulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ class FeederConfig(BaseModel):
number_of_timesteps: float
run_freq_sec: float = 15*60
start_time_index: int = 0
topology_output: str = "topology.json"


class FeederSimulator(object):
Expand Down
4 changes: 2 additions & 2 deletions LocalFeeder/sender_cosim.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@ def get_phase(name):
)

logger.info("Sending topology and saving to topology.json")
with open("topology.json", "w") as topology_file:
topology_file.write(topology.json())
with open(config.topology_output, "w") as f:
f.write(topology.json())
pub_topology.publish(topology.json())

snapshot_run(sim)
Expand Down
107 changes: 87 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,58 +1,98 @@
# GADAL-example

This example shows how to use the GADAL api to manage simulations. We also
use it as a testing ground for the testing the combination of feeders,
state estimation, and distributed OPF.

1. Install the GADAL `componentframework` using `python setup.py develop` along with its dependencies. You'll also need the HELICS CLI, `opendssdirect`, `pyarrow` in addition to the usual `scipy`, `matplotlib`, `numpy`, and `pandas`.
# Install and Running Locally

1. Install the GADAL `componentframework` using `python setup.py develop` or using
`pip install git+ssh://[email protected]/openEDI/[email protected]`.

To run the simulation, you'll need several libraries such as OpenDSSDirect.py and pyarrow.
```
pip install -r requirements.txt
```
For analysis, you'll also need matplotlib.
```
pip install 'OpenDSSDirect.py[extras]'
pip install git+https://github.com/GMLC-TDC/helics-cli.git@main
pip install pyarrow
pip install scipy matplotlib numpy pandas
pip install matplotlib`
```
2. Run `python test_full_systems.py` to initialize the system
3. Run `helics run --path=test_system_runner.json`
defined in `test_system.json` in a `build` directory.

You can specify your own directory with `--system` and your own system json
with `--system`.

3. Run `helics run --path=build/test_system_runner.json`
4. Analyze the results using `python post_analysis.py`

This computes some percentage relative errors in magnitude (MAPE) and angle (MAE),
as well as plots in `errors.png`, `voltage_magnitudes_0.png`, `voltage_angles_0.png`, etc.

If you put your outputs in a separate directory, you can run `python post_analysis.py [output_directory]`.

## Troubleshooting

If the simulation fails, you may **need** to kill the `helics_broker` manually before you can start a new simulation.

When debugging, you should check the `.log` files for errors. Error code `-9` usually occurs when it is killed by the broker as opposed to failing.
When debugging, you should check the `.log` files for errors. Error code `-9` usually occurs
when it is killed by the broker as opposed to failing directly.

# Components

All the required components are in this repo as well. Each component
also defines its own pydantic models at present.
All the required components are defined in folders within this repo. Each component
pulls types from `gadal.data_types`.

![Block diagram of simulation](sgidal-example.png)

## BasicFeeder

An OpenDSS simulation with a SMART-DS feeder. Some preprocessing has been done
(contact Junkai Liang) to match previous work. It outputs
- topology: Y-matrix, slack bus, initial phases, and unique ids for each nodes
and a labeled array with floats and `unique_ids` for each entry: (1)
`powers_real`, (2) `powers_imag`, (3) `voltages_real`, and (4) `voltages_imag`.
## Component definitions

Our components use `component_definitions.json` files in each directory to define what
their dynamic inputs and outputs are. This allows us to configure these when
we build the simulation.

## AWSFeeder

An OpenDSS simulation which loads a specified SMART-DS feeder. It outputs
- topology: Y-matrix, slack bus, initial phases
- powers
- voltages

## `measuring_federate`

The measuring federate outputs absolute voltages and complex voltages at specified nodes. The `measuring_federate` can also add Gaussian
noise with given variance.
The measuring federate can take MeasurementArray inputs and then output subsets at specified nodes.
The `measuring_federate` can also add Gaussian noise with given variance.

This federate is instantiated as multiple sensors for each type of measurement.

## `wls_federate`

The state estimation federate reads the `topology` from the feeder simulation
and measurements from the measuring federate. Then the federate outputs the
and measurements from the measuring federates. Then the federate outputs the
filled in voltages and power with angles.

## `recorder`

The `recorder` federate can connect to a subscription with a labelled array, and
then will save it to a `data.arrow` file.
The `recorder` federate can connect to a subscription with a measurement array, and
then will save it to a specified `.arrow` file as well as a `.csv` file.

This component is instantiated multiple times in the simulation for every subscription of interest.
This is similar to the HELICS observer functionality, but with more specific data types.

# How was the example constructed?

For each component, you need a `component_description.json` with
information about the inputs and outputs of each component.
We created component python scripts that matched these component
descriptions and followed the GADAL API for configuration.

In order to use the data types from other federates, the `gadal.gadal_types`
module is critical. If additional data is needed, then we recommend
subclassing the pydantic models and adding the data in the required federates
as needed. This ensures that others should still be able to parse your types if
necessary. Using compatible types is usually the most difficult part of integrating
into a system.

A basic system description with the `test_system.json` is also
needed for the simulation.
Expand All @@ -71,3 +111,30 @@ for each component with the right configuration.

![Voltage angles at time 95](voltage_angles_95.png)
![Voltage magnitudes at time 95](voltage_magnitudes_95.png)

# Docker Container

One of the downsides of having `gadal` as a private library at present is that it complicates automated
installation. We could do this by copying over a tar file, or we can use it by pip installing
it with an SSH key. Currently, we use an SSH key.

Assuming the github SSH key is in the current directory `gadal_docker_key`, we can build the docker image with
```
docker build --secret id=gadal_github_key,src=gadal_docker_key -t gadal-example:0.0.0 .
```

To get a docker volume pointed at the right place locally, we have to run more commands
```
mkdir outputs_build
docker volume create --name gadal_output --opt type=none --opt device=$(PWD)/outputs_build --opt o=bind
```

If `pwd` is unavailable on your system, then you must specify the exact path. On windows, this will end up
being `/c/Users/.../outputs_builds/`. You must use forward slashes.

Then we can run the docker image:
```
docker run --rm --mount source=gadal_output,target=/simulation/outputs gadal-example:0.0.0
```

You can omit the docker volume parts as well as `--mount` if you do not care about the exact outputs.
Loading

0 comments on commit 58423a3

Please sign in to comment.