Repository: assume-framework/assume
Pull request #578: Fixes spelling and formatting mistakes introduces with the last PR
Author: login: nick-harder
Created: 2025-05-05T12:04:16Z | Merged: 2025-05-05T12:52:50Z

The last PR has introduces several spelling and formatting mistakes. This fixes it


Diff:
diff --git a/.github/workflows/lint-pytest.yaml b/.github/workflows/lint-pytest.yaml
index a77f6187e..ab5b28d10 100644
--- a/.github/workflows/lint-pytest.yaml
+++ b/.github/workflows/lint-pytest.yaml
@@ -47,32 +47,39 @@ jobs:
 
     steps:
     - uses: actions/checkout@v4
+
     - name: Set up Python ${{ matrix.python-version }}
       uses: actions/setup-python@v5
       with:
         python-version: ${{ matrix.python-version }}
         cache: 'pip'
         cache-dependency-path: '**/pyproject.toml'
-    - name: Check reuse compliance
-      run: |
-        pip install reuse
-        reuse lint
+
+    - name: Cache pre-commit environments
+      uses: actions/cache@v3
+      with:
+        path: ~/.cache/pre-commit
+        key: ${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
+
     - name: Install dependencies
       run: |
         python -m pip install --upgrade pip
         if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
         python -m pip install -e .[test]
-    - name: Lint with ruff
-      run: |
-        # stop the build if there are Python syntax errors or undefined names
-        ruff check assume examples tests
-        ruff format assume examples tests
+
+    - name: Run pre-commit hooks
+      uses: pre-commit/action@v3.0.1
+      with:
+        extra_args: --all-files --show-diff-on-failure
+
     - name: Test with pytest & integration tests
       run: |
         pytest -m "not require_learning"
+
     - name: Install torch dependencies
       run: |
         python -m pip install -e .[learning]
+
     - name: Test pytest with torch & integration tests
       run: |
         pytest --cov --cov-report=xml --junitxml="result.xml"
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index bb66a0a41..4fa770bf4 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -27,6 +27,8 @@ repos:
   - repo: https://github.com/codespell-project/codespell
     rev: v2.3.0
     hooks:
-    -   id: codespell
+      - id: codespell
+        additional_dependencies:
+          - tomli
         types_or: [python, rst, markdown]
         files: ^(assume|docs|tests)/
diff --git a/assume/reinforcement_learning/algorithms/matd3.py b/assume/reinforcement_learning/algorithms/matd3.py
index bb1d873a2..1ad3c76e7 100644
--- a/assume/reinforcement_learning/algorithms/matd3.py
+++ b/assume/reinforcement_learning/algorithms/matd3.py
@@ -164,7 +164,7 @@ def load_critic_params(self, directory: str) -> None:
             logger.info("Agents order unchanged. Loading critic weights directly.")
         else:
             logger.info(
-                f"Agents length and/or order mismatch: n_old={len(loaded_id_order)}, n_new={len(new_id_order)}. Transfering weights for critics and target critics."
+                f"Agents length and/or order mismatch: n_old={len(loaded_id_order)}, n_new={len(new_id_order)}. Transferring weights for critics and target critics."
             )
 
         for u_id, strategy in self.learning_role.rl_strats.items():
@@ -305,13 +305,15 @@ def check_strategy_dimensions(self) -> None:
 
         if len(set(obs_dim_list)) > 1:
             raise ValueError(
-                f"All observation dimensions must be the same for all RL agents. The dfined learning strategies have the following observation dimensions: {obs_dim_list}"
+                f"All observation dimensions must be the same for all RL agents. The defined learning strategies have the following observation dimensions: {obs_dim_list}"
             )
         else:
             self.obs_dim = obs_dim_list[0]
 
         if len(set(act_dim_list)) > 1:
-            raise ValueError(f"All action dimensions must be the same for all RL agents. The defined learning strategies have the following action dimensions: {act_dim_list}")
+            raise ValueError(
+                f"All action dimensions must be the same for all RL agents. The defined learning strategies have the following action dimensions: {act_dim_list}"
+            )
         else:
             self.act_dim = act_dim_list[0]
 
diff --git a/assume/reinforcement_learning/learning_role.py b/assume/reinforcement_learning/learning_role.py
index b8696e50c..2fb7432bf 100644
--- a/assume/reinforcement_learning/learning_role.py
+++ b/assume/reinforcement_learning/learning_role.py
@@ -228,7 +228,7 @@ def turn_off_initial_exploration(self, loaded_only=False) -> None:
 
         If `loaded_only=True`, only turn off exploration for strategies that were loaded (used in continue_learning mode).
         If `loaded_only=False`, turn it off for all strategies.
-        
+
         Args:
             loaded_only (bool): Whether to disable exploration only for loaded strategies.
         """
diff --git a/assume/reinforcement_learning/learning_utils.py b/assume/reinforcement_learning/learning_utils.py
index 8956df22d..4d938e5c2 100644
--- a/assume/reinforcement_learning/learning_utils.py
+++ b/assume/reinforcement_learning/learning_utils.py
@@ -200,20 +200,20 @@ def transfer_weights(
     """
     Transfer weights from loaded model to new model. Copy only those obs_ and action-slices for matching IDs.
     New IDs keep their original (random) weights. Function only works if the neural network architeczture remained stable besides the input layer, namely with the same hidden layers.
-    
+
     Args:
         model (th.nn.Module): The model to transfer weights to.
         loaded_state (dict): The state dictionary of the loaded model.
         loaded_id_order (list[str]): The list of unit IDs from the loaded model that shows us the order of units.
-        new_id_order (list[str]): The list of IDs from the new model, inlcudes potentially different agents in comparison to the loaded model.
+        new_id_order (list[str]): The list of IDs from the new model, includes potentially different agents in comparison to the loaded model.
         obs_base (int): The base observation size.
         act_dim (int): The action dimension size.
         unique_obs (int): The unique observation size per agent, smaller than obs_base as these include also shared observation values.
-        
+
     returns:
         dict | None: The updated state dictionary with transferred weights, or None if architecture mismatch.
     """
-    
+
     # 1) Architecture check
     new_state = model.state_dict()
     loaded_hidden = get_hidden_sizes(loaded_state, prefix="q1_layers")
@@ -245,7 +245,7 @@ def transfer_weights(
         # a) shared obs_base
         w_new[:, :obs_base] = w_loaded[:, :obs_base]
 
-        # b) matched agents’ ID 
+        # b) matched agents’ ID
         # copy weights from loaded to new model
         for new_idx, u in enumerate(new_id_order):
             if u not in loaded_id_order:
@@ -261,10 +261,12 @@ def transfer_weights(
             # action blocks for every agent
             new_act = new_obs_tot + act_dim * new_idx
             loaded_act = loaded_obs_tot + act_dim * loaded_idx
-            w_new[:, new_act : new_act + act_dim] = w_loaded[:, loaded_act : loaded_act + act_dim]
+            w_new[:, new_act : new_act + act_dim] = w_loaded[
+                :, loaded_act : loaded_act + act_dim
+            ]
 
-        # c) unmatched agents’ ID 
-        # use randomly initilized weights for unmatched agents
+        # c) unmatched agents’ ID
+        # use randomly initialized weights for unmatched agents
         for new_idx, u in enumerate(new_id_order):
             if new_idx == 0 or u in loaded_id_order:
                 continue
@@ -279,6 +281,8 @@ def transfer_weights(
             new_state_copy[f"{prefix}.{i}.weight"].copy_(
                 loaded_state[f"{prefix}.{i}.weight"]
             )
-            new_state_copy[f"{prefix}.{i}.bias"].copy_(loaded_state[f"{prefix}.{i}.bias"])
+            new_state_copy[f"{prefix}.{i}.bias"].copy_(
+                loaded_state[f"{prefix}.{i}.bias"]
+            )
 
     return new_state_copy
diff --git a/assume/strategies/naive_strategies.py b/assume/strategies/naive_strategies.py
index c32bd0512..682975f4a 100644
--- a/assume/strategies/naive_strategies.py
+++ b/assume/strategies/naive_strategies.py
@@ -363,7 +363,7 @@ def calculate_bids(
 class ElasticDemandStrategy(BaseStrategy):
     """
     A bidding strategy for a demand unit that submits multiple bids to approximate
-    a marginal utility curve, based on linear or isoelastic demand theory. 
+    a marginal utility curve, based on linear or isoelastic demand theory.
     P = Price, Q = Quantity, E = Elasticity.
 
     - Linear model: P = P_max + slope * Q (slope is only defined by P_max and Q_max, negative value)
@@ -384,9 +384,8 @@ def calculate_bids(
         product_tuples: list[Product],
         **kwargs,
     ) -> Orderbook:
-                
         bids = []
-        
+
         for product in product_tuples:
             start, end, only_hours = product
             max_abs_power = max(abs(unit.min_power), abs(unit.max_power))
@@ -402,14 +401,14 @@ def calculate_bids(
                 #   integrate:
                 #   \int 1/Q dQ = E * \int 1/P dP
                 #   ln(Q) = E * ln(P) + C
-                #   exp(ln(Q)) = exp(E * ln(p) + C)  
+                #   exp(ln(Q)) = exp(E * ln(p) + C)
                 #   Q(p) = (exp(ln(p))^E) * exp(C)
                 #   Q(p) = P^E * exp(C)
                 #   possibly C = 0, C = 1 or C >= 1. We assume C >= 1.
                 #   C shifts the demand curve (demand vs. price) up / down and
                 #   can be interpreted as the demand that will always be served
                 #   we set C = ln(Q_max) and derive the demand curve from there:
-                #   P = Q^(1/E) * exp(-C/E) and C = ln(Q_max) 
+                #   P = Q^(1/E) * exp(-C/E) and C = ln(Q_max)
                 #   => P = Q^(1/E) * exp(-ln(Q_max)/E) and because exp(-ln(Q_max)/E) = Q_max^(-1/E)
                 #   => P = Q^(1/E) * Q_max^(-1/E)
                 #   finally
@@ -430,7 +429,7 @@ def calculate_bids(
                         "node": unit.node,
                     }
                 )
-            
+
                 remaining_volume = max_abs_power - first_bid_volume
                 if remaining_volume < 0:
                     raise ValueError(
@@ -468,12 +467,12 @@ def calculate_bids(
 
                     bids.append(
                         {
-                        "start_time": start,
-                        "end_time": end,
-                        "only_hours": only_hours,
-                        "price": bid_price,
-                        "volume": -bid_volume,
-                        "node": unit.node,
+                            "start_time": start,
+                            "end_time": end,
+                            "only_hours": only_hours,
+                            "price": bid_price,
+                            "volume": -bid_volume,
+                            "node": unit.node,
                         }
                     )
 
@@ -486,7 +485,7 @@ def find_first_block_bid(
         Calculate the first block bid volume at max_price. P = Price, Q = Quantity, E = Elasticity.
         Assumes isoelastic demand:
             Q = Q_max * P^E
-        
+
         The first block bid is the volume that is always bid at maximum price, because the
         willingness to pay for it is higher than the markets maximal price.
         The first block bid volume is calculated by finding the intersection of the isoelastic demand
@@ -495,14 +494,14 @@ def find_first_block_bid(
 
             Q_first = Q
             Q_first = Q_max * P^E
-    
+
         Therefore:
             Q_first = max_power * (max_price ** E)
 
         Returns:
             float: Volume > 0, demand that is always bought at max willingness to pay
         """
-        volume = max_power * max_price**elasticity 
+        volume = max_power * max_price**elasticity
 
         if abs(volume) > abs(max_power):
             raise ValueError(
diff --git a/assume/units/demand.py b/assume/units/demand.py
index 4fe045aa4..6fe57cb9e 100644
--- a/assume/units/demand.py
+++ b/assume/units/demand.py
@@ -80,16 +80,22 @@ def __init__(
                     f"Invalid elasticity_model '{self.elasticity_model}' at unit {self.id}. Choose 'linear' or 'isoelastic'."
                 )
             if self.num_bids <= 1:
-                raise ValueError(f"'num_bids' parameter must be >= 1 for elastic demand at unit {self.id}")
+                raise ValueError(
+                    f"'num_bids' parameter must be >= 1 for elastic demand at unit {self.id}"
+                )
             if self.elasticity_model == "isoelastic":
                 if self.elasticity >= 0.0:
-                    raise ValueError(f"'elasticity' parameter must be given and negative for isoelastic demand at unit {self.id}.")
+                    raise ValueError(
+                        f"'elasticity' parameter must be given and negative for isoelastic demand at unit {self.id}."
+                    )
             if self.elasticity_model == "linear":
-                if -(self.max_price / max(abs(self.min_power), abs(self.max_power))) >= 0.0:
+                if (
+                    -(self.max_price / max(abs(self.min_power), abs(self.max_power)))
+                    >= 0.0
+                ):
                     raise ValueError(
                         f"Invalid slope of demand curve at unit {self.id}. Slope must be negative for linear demand. Set 'max_price' positive."
                     )
-            
 
     def execute_current_dispatch(
         self,
diff --git a/docs/source/learning_algorithm.rst b/docs/source/learning_algorithm.rst
index 5a2dfce4f..48910e067 100644
--- a/docs/source/learning_algorithm.rst
+++ b/docs/source/learning_algorithm.rst
@@ -53,7 +53,7 @@ The following table shows the options that can be adjusted and gives a short exp
   early_stopping_threshold                The value by which the average reward needs to improve to avoid early stopping.
  ======================================== ==========================================================================================================
 
-How to use continue learning 
+How to use continue learning
 ----------------------------
 
 The continue learning function allows you to load pre-trained strategies (actor and critic networks) and continue the learning process with these networks.
@@ -64,7 +64,7 @@ The learning process will then start from these pre-trained networks instead of
 
 In other words, the input layer of the critics will vary depending on the number of agents. To enable the use of continue learning between simulations with varying agent sizes, a mapping is implemented that ensures the loaded critics are adapted to match the new number of agents.
 
-This process will fail, when the number of hidden layers differes between the loaded critic and the new critic. In this case, you will need to retrain the networks from scratch. Further, different chosen neural network arhcitectures for the critic (or actor) between the loaded and new networks will also lead to a failure of the continue learning process. 
+This process will fail, when the number of hidden layers differs between the loaded critic and the new critic. In this case, you will need to retrain the networks from scratch. Further, different chosen neural network arhcitectures for the critic (or actor) between the loaded and new networks will also lead to a failure of the continue learning process.
 
 
 The Algorithms
diff --git a/docs/source/units.rst b/docs/source/units.rst
index 28f13e8b4..2af818340 100644
--- a/docs/source/units.rst
+++ b/docs/source/units.rst
@@ -14,7 +14,7 @@ The primary unit types in this context include:
 
 2. **Storage Units**: These units, like batteries or pumped hydro storage, can store electricity when supply exceeds demand and release it when needed, adding flexibility to the grid.
 
-3. **Demand Units**: These represent consumers of electricity, such as households, industries, or commercial buildings, whose electricity consumption is typically fixed and not easily adjustable based on real-time grid conditions. Demand units will therefore be modelled with inelastic demand most often. However, representation of elastic bidding is possible with this unit type. 
+3. **Demand Units**: These represent consumers of electricity, such as households, industries, or commercial buildings, whose electricity consumption is typically fixed and not easily adjustable based on real-time grid conditions. Demand units will therefore be modelled with inelastic demand most often. However, representation of elastic bidding is possible with this unit type.
 
 Each unit type has specific characteristics that affect how the power system operates, and understanding these is key to modelling and optimizing grid performance.
 
diff --git a/tests/test_demand.py b/tests/test_demand.py
index dc65de0b4..d79b1ad13 100644
--- a/tests/test_demand.py
+++ b/tests/test_demand.py
@@ -194,7 +194,10 @@ def test_elastic_demand_config_and_errors():
         assert "price" in bid and "volume" in bid and bid["price"] > 0
 
     # Invalid: elasticity is positive (isoelastic model)
-    with pytest.raises(ValueError, match="'elasticity' parameter must be given and negative for isoelastic demand"):
+    with pytest.raises(
+        ValueError,
+        match="'elasticity' parameter must be given and negative for isoelastic demand",
+    ):
         Demand(
             id="bad_elasticity",
             unit_operator="UO1",


Reviews:
user: login: maurerle
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-05T12:31:19Z
user: login: maurerle
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-05T12:41:59Z
user: login: nick-harder
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-05T12:44:18Z
user: login: nick-harder
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-05T12:50:13Z
user: login: maurerle
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-05T12:53:53Z
user: login: nick-harder
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-05T14:43:51Z

Review comments:
diff_hunk: @@ -30,3 +30,7 @@ repos:
     -   id: codespell
         types_or: [python, rst, markdown]
         files: ^(assume|docs|tests)/
+        args:
+          # comma-separated list of words you want codespell to skip
+          - --ignore-words-list=DED,esy,flexable,Meger,ore
+          - --ignore-regex=https?://\S+
path: .pre-commit-config.yaml
user: login: maurerle
body: Doesn't it work to have this here?
https://github.com/assume-framework/assume/blob/main/pyproject.toml#L152
created_at: 2025-05-05T12:41:59Z
author_association: MEMBER
diff_hunk: @@ -30,3 +30,7 @@ repos:
     -   id: codespell
         types_or: [python, rst, markdown]
         files: ^(assume|docs|tests)/
+        args:
+          # comma-separated list of words you want codespell to skip
+          - --ignore-words-list=DED,esy,flexable,Meger,ore
+          - --ignore-regex=https?://\S+
path: .pre-commit-config.yaml
user: login: nick-harder
body: @maurerle idk why, but it fails without this argument. It's stupid to keep at at two places
created_at: 2025-05-05T12:44:18Z
author_association: MEMBER
diff_hunk: @@ -30,3 +30,7 @@ repos:
     -   id: codespell
         types_or: [python, rst, markdown]
         files: ^(assume|docs|tests)/
+        args:
+          # comma-separated list of words you want codespell to skip
+          - --ignore-words-list=DED,esy,flexable,Meger,ore
+          - --ignore-regex=https?://\S+
path: .pre-commit-config.yaml
user: login: nick-harder
body: @maurerle ok fixed by pointing to tomli
created_at: 2025-05-05T12:50:13Z
author_association: MEMBER
diff_hunk: @@ -30,3 +30,7 @@ repos:
     -   id: codespell
         types_or: [python, rst, markdown]
         files: ^(assume|docs|tests)/
+        args:
+          # comma-separated list of words you want codespell to skip
+          - --ignore-words-list=DED,esy,flexable,Meger,ore
+          - --ignore-regex=https?://\S+
path: .pre-commit-config.yaml
user: login: maurerle
body: Well yes.. Since python 3.11 tomllib is part of the standard library, so it failed for python 3.10.
Looks fine
created_at: 2025-05-05T12:53:53Z
author_association: MEMBER
diff_hunk: @@ -30,3 +30,7 @@ repos:
     -   id: codespell
         types_or: [python, rst, markdown]
         files: ^(assume|docs|tests)/
+        args:
+          # comma-separated list of words you want codespell to skip
+          - --ignore-words-list=DED,esy,flexable,Meger,ore
+          - --ignore-regex=https?://\S+
path: .pre-commit-config.yaml
user: login: nick-harder
body: @maurerle yeah maybe we should already drop 3.10 at some point :-)
created_at: 2025-05-05T14:43:51Z
author_association: MEMBER

PR comments:
user: login: nick-harder
created_at: 2025-05-05T12:04:59Z
body: @kim-mskw and @gugrimm I believe your pre-commit is outdated, maybe you should delete and upgrade it, as it would have caught such mistakes
author_association: MEMBER
user: login: codecov[bot]
created_at: 2025-05-05T12:07:10Z
body: ## [Codecov](https://app.codecov.io/gh/assume-framework/assume/pull/578?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=assume-framework) Report
:white_check_mark: All modified and coverable lines are covered by tests.
:white_check_mark: Project coverage is 79.67%. Comparing base ([`1c94172`](https://app.codecov.io/gh/assume-framework/assume/commit/1c941721bf708998b3967bdf595461411fddd7a1?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=assume-framework)) to head ([`e9eb799`](https://app.codecov.io/gh/assume-framework/assume/commit/e9eb799822583e80b8ef627da87e0ec825e54065?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=assume-framework)).
:warning: Report is 73 commits behind head on main.

<details><summary>Additional details and impacted files</summary>



```diff
@@           Coverage Diff           @@
##             main     #578   +/-   ##
=======================================
  Coverage   79.67%   79.67%           
=======================================
  Files          52       52           
  Lines        7416     7416           
=======================================
  Hits         5909     5909           
  Misses       1507     1507           
```

| [Flag](https://app.codecov.io/gh/assume-framework/assume/pull/578/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=assume-framework) | Coverage Δ | |
|---|---|---|
| [pytest](https://app.codecov.io/gh/assume-framework/assume/pull/578/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=assume-framework) | `79.67% <100.00%> (ø)` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=assume-framework#carryforward-flags-in-the-pull-request-comment) to find out more.
</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/assume-framework/assume/pull/578?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=assume-framework).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=assume-framework).
<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE
user: login: nick-harder
created_at: 2025-05-05T12:42:15Z
body: @maurerle I have adjusted the test workflow to run the full pre-commit workflow. Fine by you?
author_association: MEMBER