Skip to content

Conversation

@Graf-Wronski
Copy link
Contributor

Related Issue

Relates to #664 .

Description

This branch featues a draft for config validation and respective tests.

Checklist

  • Documentation updated (docstrings, READMEs, user guides, inline comments, doc folder updates etc.)
  • New unit/integration tests added (if applicable)
  • Changes noted in release notes (if any)
  • Consent to release this PR's code under the GNU Affero General Public License v3.0

Comment on lines 706 to 714
for market_id, market_config in self.markets.items():
market_start = market_config.opening_hours[0]
market_end = market_config.opening_hours[-1]

if market_start < self.start or market_end > self.end:
msg = (f"Market {market_id} violates world schedule. \n"
f"Market start: {market_start}, end: {market_end}. \n)"
f"World start: {market_start}, end: {market_end}.)")
raise ValueError(msg)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe its best to move this to the add_market method so the simulation fails even earlier, when adding an invalid market 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you are right, that add_market would be the best place for this part if we organize validation locally. Regarding a possible DataValidation class, to me it sounded more like that the validation is intended to be done centrally for all data.

Comment on lines +718 to +725
unit_operators = list(self.unit_operators.values())
for operator in unit_operators:
for market_id in operator.portfolio_strategies.keys():
if market_id not in list(self.markets.keys()):
msg = (f"Strategies of unit operator {operator} references"
f"market {market_id} which is not known in world."
f"Known markets are:\n{list(self.markets.keys())}.")
raise ValueError(msg)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see above, this may be moved to the add_unit_operator method

Comment on lines +731 to +734
for market_id in self.markets.keys():
if not market_id in referenced_markets:
msg = f"Added market {market_id}, has no participants."
warnings.warn(msg)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this part is already ensured by the unit_operator constructor, where a new unit operator references all available markets automatically (i may be wrong though)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. You are right, there is a similar logic in UnitsOperator.__init__. However (and this is why the corresponding test works): You theoretically can add a market to World after you added all UnitsOperators.

This makes we wonder what the purpose of the respective logic in UnitsOperator.__init__ is.

for market in self.available_markets:
            if market.market_id not in self.portfolio_strategies.keys():
                self.portfolio_strategies[market.market_id] = (
                    DirectUnitOperatorStrategy()
                )

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You theoretically can add a market to World after you added all UnitsOperators.

Yes, theoretically - it is currently not supported though. So one first has to create the markets and then add the participants.

Fixing this would require to somehow inject the information of available markets prior to the start of the simulation into all UnitsOperators, which would add another layer of magic/complexity to the simulation.

Copy link
Contributor Author

@Graf-Wronski Graf-Wronski Nov 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am a little confused. Does "Not supported" mean a code like this should raise an Exception?

world = setup_simple_world()

@pytest.fixture
def demand():
    return Demand(
        id="test_unit",
        unit_operator="test_operator",
        min_power=0,
        max_power=-1000,
        technology="demand",
        bidding_strategies={},
        forecaster=DemandForecaster(index, demand=-100),
    )


def test_addition_order(demand):
    """ Add participants before market. """
    
    
    world.add_unit_operator(
        "unit_operator",
        {"dispatch": "naive_eom"})
    world.add_unit_instance(
        operator_id="unit_operator",
        unit=demand)
    
    opening_dispatch = rr.rrule(rr.HOURLY, dtstart=world.start, until=world.end)
    
    market_products= [MarketProduct(
        duration=datetime.timedelta(hours=1),
        count=1,
        first_delivery=datetime.timedelta(hours=1))]
    
    dispatch_config = MarketConfig(
        "dispatch",
        opening_hours=opening_dispatch,
        market_products=market_products)
    
    world.add_market_operator("market_operator")
    world.add_market(
        market_operator_id="market_operator",
        market_config=dispatch_config)
    
    world.run()

@jrasko jrasko requested a review from a team November 18, 2025 16:16
msg = f"Added market {market_id}, has no participants."
warnings.warn(msg)

# Real-world integrity.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be perfect for the post_init method of the config, right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After creation of the market, you do not know if there will be participants.
After creation of a single agent, you do not know if there will be further agents.
So the only place to check if there is any UnitsOperator, which has a strategy for this market configured is right prior to the run execution call of the simulation, by iterating over all units of all UnitsOperators and checking the BiddingStrategies..

@kim-mskw
Copy link
Contributor

Thanks @Graf-Wronski! I think the general discussion we have to have is whether this should happen centrally or decentralized. I noticed that @jrasko and I immediately suggested where these have to go in the framework.

Two things to consider for this:
What would be the best way to do it concerning the potential reusage in the GUI @jrasko. Or is reusage not an option? Remind me again.

What is the most natural way if one wants to extend these? Change a validation call in the unit-operator and market, and post_init, or in one validator class? What do you think @Graf-Wronski?

@jrasko
Copy link
Contributor

jrasko commented Nov 19, 2025

@kim-mskw for now, it is irrelevant for the frontend when the validation takes place, since simulations are executed immeadiatly after creation.
However, I would suggest you consider the following:
Decentralized validation can prevent a World to be missconfigured at any time, so that future features (e.g. saving a created world and running it later or on a different machine) are ensured to use a valid configuration at all times. We also use this method already when creating units.
Centralized validation may provide a better overview on which validations exists.
I would therefore like to hear some more oppinions on this matter

@kim-mskw
Copy link
Contributor

@kim-mskw for now, it is irrelevant for the frontend when the validation takes place, since simulations are executed immeadiatly after creation. However, I would suggest you consider the following: Decentralized validation can prevent a World to be missconfigured at any time, so that future features (e.g. saving a created world and running it later or on a different machine) are ensured to use a valid configuration at all times. We also use this method already when creating units. Centralized validation may provide a better overview on which validations exists. I would therefore like to hear some more oppinions on this matter

I am not sure I see the use case of creating a world and then saving it to deploy it somewhere else. When and why would I do that?

@Graf-Wronski
Copy link
Contributor Author

Thanks @Graf-Wronski! I think the general discussion we have to have is whether this should happen centrally or decentralized. I noticed that @jrasko and I immediately suggested where these have to go in the framework.

Two things to consider for this: What would be the best way to do it concerning the potential reusage in the GUI @jrasko. Or is reusage not an option? Remind me again.

What is the most natural way if one wants to extend these? Change a validation call in the unit-operator and market, and post_init, or in one validator class? What do you think @Graf-Wronski?

I think with regards to extensibility, a central validation class would be advantageous since already implemented validations are more accesible. Also functions are shorter if validation is outsourced, which I (personally) prefer. The big question mark for central validation however is, where in the pipeline is it triggered.

@kim-mskw

This comment was marked as duplicate.

for x in dispatch_markets)
earliest_redispatch_opening = min(x.opening_hours[0]
for x in redispatch_markets)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic will not work for markets that open multiple times.

x.opening_hours[0] + x.opening_duration

seems to be a applicable alterantive.

@Graf-Wronski

This comment was marked as duplicate.

@kim-mskw

This comment was marked as duplicate.

@kim-mskw
Copy link
Contributor

Also, another thing to check. If one wants to use the compley_clearing but does not specify the param_dict it will just give a weird error that the nodes do not match because it initialisses with a default node

  market_mechanism: complex_clearing
  param_dict:
    network_path: .
    solver: highs
    zones_identifier: zone_id
    pricing_mechanism: pay_as_clear

@maurerle
Copy link
Member

maurerle commented Nov 20, 2025

I would differentiate between independent misconfigurations and depending misconfigurations.

Generally, I would fail as early as possible - see also.
I think that it makes sense to have the validation at different places.
Otherwise, we would need to have all places which raise a ValueError in some kind of validator..?

I do see good use of a validation method for a pre-run check though.
This would cover

@maurerle
Copy link
Member

@Graf-Wronski this issue is a little bit like a whish list and I guess that some of the comments here should rather be placed as additional entries in #664

This might be overwhelming at first, but remember to always do small steps without trying to solve everything at once :)

@kim-mskw
Copy link
Contributor

kim-mskw commented Nov 20, 2025

@Graf-Wronski this issue is a little bit like a whish list and I guess that some of the comments here should rather be placed as additional entries in #664

This might be overwhelming at first, but remember to always do small steps without trying to solve everything at once :)

Moved them to the issue :)

@Graf-Wronski
Copy link
Contributor Author

So I have been thinking a little a bit about the optimal placement for validation and why it appears a little controversial and tried to summarize our discussion. I think one potential source of controversy is the duality of file-based and script-based world creation.

If there was only file-based world creation, I think the best approach would be to load the data and immediately within the DataLoader validate the data (#failearly) before creating a world.

For the creation of a world step by step within a script, the case is more complex. There are some validations that appear a little more intuitive when conducted by the respective add_* method, e.g. when adding a market to the world, check if its 'start_time' fits simulation time. Others can not be conducted before world.run() as their validity depends on potential later world changes, e.g. as above when you want to check if a market is actually referenced.

Now there are these three possible validation locations:

  1. Validation in DataLoader.
  2. Validation in respective World.add_*
  3. Valiation at the beginning of World.run()

Location 3 is the only one that is suitable as standalone method.

Location 1 can only work as a standalone method for file_based scenarios, while location 2 can only work in combination with location 3.

If I understand @maurerle correctly, you vote for location 2 where it is possible and location 3 where it is necessary.

I personally would go for validation only at location 3 but I could live with 2+3.

Are there any other opinions?

And does anyone see further need for validation for loaded data (at location 1)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants