|
1 | 1 | # Dots Calculation service template |
2 | | -A template repository for DOTs-helics calculation services. |
3 | | -To create a new calculation service follow the following steps (For a more detailed explenation see below): |
4 | | -1. Create a new github repository based on this template. |
5 | | -2. Edit the `EConnection.py` based upon your needs i.e. define the correct calculations for the calculation service. |
6 | | -3. Edit the `TestTemplate.py` to test your calculations indepedently in a python unit test. |
7 | | -4. Replace the placeholders in the `Dockerfile`. The foldername should match the one that is in the src folder. |
| 2 | +A template repository for DOTs-helics calculation services. |
8 | 3 |
|
9 | | -## Creating a calculation in the calculation service |
10 | | -The initial `EConnection.py` defines two calculations inside the `EConnection` calculation service. These calculations are called `EConnectionDispatch` and `EConnectionSchedule` respectively. Let's take a look at the definition of the first calculation: |
11 | | - |
12 | | -```python |
13 | | -class CalculationServiceEConnection(HelicsSimulationExecutor): |
14 | | - |
15 | | - def __init__(self): |
16 | | - super().__init__() |
17 | | - |
18 | | - subscriptions_values = [ |
19 | | - SubscriptionDescription(esdl_type="PVInstallation", |
20 | | - input_name="PV_Dispatch", |
21 | | - input_unit="W", |
22 | | - input_type=h.HelicsDataType.DOUBLE) |
23 | | - ] |
24 | | - |
25 | | - publication_values = [ |
26 | | - PublicationDescription(global_flag=True, |
27 | | - esdl_type="EConnection", |
28 | | - output_name="EConnectionDispatch", |
29 | | - output_unit="W", |
30 | | - data_type=h.HelicsDataType.DOUBLE) |
31 | | - ] |
32 | | - |
33 | | - e_connection_period_in_seconds = 60 |
34 | | - |
35 | | - calculation_information = HelicsCalculationInformation( |
36 | | - time_period_in_seconds=e_connection_period_in_seconds, |
37 | | - offset=0, |
38 | | - uninterruptible=False, |
39 | | - wait_for_current_time_update=False, |
40 | | - terminate_on_error=True, |
41 | | - calculation_name="EConnectionDispatch", |
42 | | - inputs=subscriptions_values, |
43 | | - outputs=publication_values, |
44 | | - calculation_function=self.e_connection_dispatch |
45 | | - ) |
46 | | - self.add_calculation(calculation_information) |
47 | | - |
48 | | - publication_values = [ |
49 | | - PublicationDescription(True, "EConnection", "Schedule", "W", h.HelicsDataType.VECTOR) |
50 | | - ] |
51 | | - |
52 | | - e_connection_period_in_seconds = 21600 |
53 | | - |
54 | | - calculation_information_schedule = HelicsCalculationInformation(e_connection_period_in_seconds, 0, False, False, True, "EConnectionSchedule", [], publication_values, self.e_connection_da_schedule) |
55 | | - self.add_calculation(calculation_information_schedule) |
56 | | - |
57 | | - def init_calculation_service(self, energy_system: esdl.EnergySystem): |
58 | | - LOGGER.info("init calculation service") |
59 | | - for esdl_id in self.simulator_configuration.esdl_ids: |
60 | | - LOGGER.info(f"Example of iterating over esdl ids: {esdl_id}") |
61 | | - |
62 | | - def e_connection_dispatch(self, param_dict : dict, simulation_time : datetime, time_step_number : TimeStepInformation, esdl_id : EsdlId, energy_system : EnergySystem): |
63 | | - ret_val = {} |
64 | | - single_dispatch_value = get_single_param_with_name(param_dict, "PV_Dispatch") # returns the first value in param dict with "PV_Dispatch" in the key name |
65 | | - all_dispatch_values = get_vector_param_with_name(param_dict, "PV_Dispatch") # returns all the values as a list in param_dict with "PV_Dispatch" in the key name |
66 | | - ret_val["EConnectionDispatch"] = sum(single_dispatch_value) |
67 | | - self.influx_connector.set_time_step_data_point(esdl_id, "EConnectionDispatch", simulation_time, ret_val["EConnectionDispatch"]) |
68 | | - return ret_val |
69 | | -``` |
70 | | - |
71 | | -First, you can see a list called `subscriptions_values`. This list defines the inputs of the calculation. In this case it hase one input that is supposed to come from the ESDL type called `PVInstallation` as specified by the `esdl_type` parameter. Next, the `input_name` parameter describes the name of the value that the `PVInstallation` produces. Finally, the `input_unit` and `input_type` describe the input's unit and type respectively. |
72 | | - |
73 | | -The second list describes the publications or outputs of the calculation. There are two parameters of a publication description that are worth mentioning: `global_flag` this needs to be set to `True` and will be removed in later versions of this template, and, `esdl_type` this is the esdl type of the calculation service that you are defining in this case `EConnection`. |
74 | | - |
75 | | -After the definition of the subscription and publication values the actual calculation is defined by instantiating `HelicsCalculationInformation`. The first 5 parameters are related to a helics federate confguration and therefore the reader is reffered to the [helics documentation](https://docs.helics.org/en/latest/references/configuration_options_reference.html#broker_init_string--null). |
76 | | - |
77 | | -Finally, the the last line adds the calculation to the calculation service and ensures that it is executed during a running co-simulation. Every added calculation will become a [helics federate](https://docs.helics.org/en/latest/user-guide/fundamental_topics/helics_terminology.html) with their own timing parameters as defined in the `calculation_information`. To get an idea of how helics timing works have a look at this [page](https://docs.helics.org/en/latest/user-guide/fundamental_topics/timing_configuration.html) of the helics documentation. |
78 | | - |
79 | | -When the simulation starts there will be an initialization stage and a calculating stage. In the initialization phase a calculation service can initialize variables that are required in the calculation stage. This can be done by adjusting the `init_calculation_service` function. The esdl that is associated with the simulated scenario is given as a parameter to this function. |
80 | | - |
81 | | -In the calculation phase the calculation functions are called periodically for each simulated esdl entity. The `esdl_id` of the simulated entity is passed as a parameter to the calculation function. New inputs can be read and new outputs can be generated. An example of getting inputs, returning outputs and writing to the influx db can be found in the example above. |
82 | | - |
83 | | -## Getting inputs from a calculation service |
84 | | -The input parameters provided by other calculation services are provided by the `param_dict` parameter in the calculation. Wheneve the calculation function `e_connection_dispatch` is called, the param dict for the calculation `e_connection_dispatch` could look like: |
85 | | - |
86 | | -``` |
87 | | -param_dict = { |
88 | | - "PVInstallation/PV_Dispatch/1f60ceb9-9708-4d89-b079-482abc1408ea" : 5, |
89 | | - "PVInstallation/PV_Dispatch/468f4332-4306-4b74-a5c2-eb8a7aa0a8d9" : 3, |
90 | | -} |
91 | | -``` |
92 | | - |
93 | | -This would mean that the associated esdl entity is connected to two `PVInstallation` entities with id `1f60ceb9-9708-4d89-b079-482abc1408ea` and `468f4332-4306-4b74-a5c2-eb8a7aa0a8d9` respectively. There are two ways to retrieve the values from the dictionary. First, by the python way of retrieving values from a dictiononary i.e. `param_dict[key]` this would require know the keys of dictionary. |
94 | | -The second option is to use the helper functions in `dots_infrastructure.CalculationServiceHelperFunctions` (as shown in the above example). The function `get_single_param_with_name` will get the first value in `param_dict` with a specific input name. In the above example the input called `PV_Dispatch` is fetched and thus the function will return the vaule `5`. The other function to help retrieve values is called `get_vector_param_with_name` and will return all the values with a specific `input_name` as list. In this example it wil return the list `[5, 3]`. |
95 | | - |
96 | | -## Testing a calculation service |
97 | | - |
98 | | -1. Create a new python virtual environment |
99 | | -2. Install dependencies `pip install -r requirements.txt` |
100 | | -3. Install package `pip install -e .` |
101 | | -4. Run `cd test` |
102 | | -5. Run `python -m unittest discover -s ./ -p 'Test*.py'` |
103 | | - |
104 | | -## Building a docker image such that it can be used |
105 | | - |
106 | | -1. Adjust `<<ImageName>>` to the name of the calculation service's image in the file `.github/workflows/publish-image.yml` |
107 | | -2. Push your changes to a new branch |
108 | | -3. Create a pull request |
109 | | -4. A github action will now run building the calculation service as a docker image and pushing it to the registry, as long as the pull request is not merged in the main branch the version number will be `test` |
110 | | -5. When finished complete the pull request and a new docker image will be built and pushed with version number `latest` |
111 | | -6. Change the visibility of the package to public, follow the steps detail [here](https://docs.github.com/en/enterprise-server@3.12/packages/learn-github-packages/configuring-a-packages-access-control-and-visibility#configuring-visibility-of-packages-for-an-organization). |
| 4 | +Find the instructions on how to use this template in `template-instructions.md`. |
0 commit comments