Skip to content

feat(discrete_mode_choice): detailed utilities writer#4188

Open
Dib-AEK wants to merge 3 commits intomatsim-org:mainfrom
Dib-AEK:detailedUtilitiesWriter
Open

feat(discrete_mode_choice): detailed utilities writer#4188
Dib-AEK wants to merge 3 commits intomatsim-org:mainfrom
Dib-AEK:detailedUtilitiesWriter

Conversation

@Dib-AEK
Copy link

@Dib-AEK Dib-AEK commented Aug 12, 2025

Summary
Adds a new boolean config attribute writeDetailedUtilities (default: false).
When enabled, it writes a CSV with all considered mode options and their utilities for each considered tour, and which of the tours is finally selected for the agent plan.

Purpose

  • Supports calibration of the discrete mode choice model
  • Helps analyze and debug mode choice decisions

Impact

  • No change to default behavior

Config Example:

DiscreteModeChoice { 
     selector:MultinomialLogit {
          writeDetailedUtilities = true
                                 }
                    }

@Dib-AEK Dib-AEK changed the title feat : Detailed utilities writer feat: Detailed utilities writer Aug 12, 2025
@balacmi balacmi self-assigned this Aug 14, 2025
public static final String MINIMUM_UTILITY = "minimumUtility";
public static final String MAXIMUM_UTILITY = "maximumUtility";
public static final String CONSIDER_MINIMUM_UTILITY = "considerMinimumUtility";
public static final String WRITE_DETAILED_UTILITIES = "writeDetailedUtilities";
Copy link
Contributor

Choose a reason for hiding this comment

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

Why add this variable here and not in the DiscreteModeChoiceConfigGroup? It should be more general and not only for the MultinomialLogitSelector.

this.minimumUtility = minimumUtility;
this.considerMinimumUtility = considerMinimumUtility;
this.writeDetailedUtilities = writeDetailedUtilities;
this.personId = (person!=null)? person.getId(): Id.create("unknown", Person.class);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this necessary? If the person==null then we have a problem


// ===== WRITE TO CSV HERE IF REQUESTED IN THE CONFIG =====
if (writeDetailedUtilities && UtilityWriter.isWriterInitialized()) {
UtilityWriter.writeCandidate(personId, tourTrips, filteredCandidates, selection);
Copy link
Contributor

Choose a reason for hiding this comment

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

This will cause a lot of locking during the mode choice, as we want to write things immediately. Would it be better if we stored the utilities and wrote them out at once later? There is probably some tradeoff between memory and speed.

@Override
public UtilitySelector createUtilitySelector() {
public UtilitySelector createUtilitySelector(Person person, List<DiscreteModeChoiceTrip> tourTrips) {
return new MaximumSelector();
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are the person and tourTrips not passed to the MaximumSelector? That one should also record utilities if desired. The same for all the other possible selectors: i.e.,RandomSelector

@Override
public NestedLogitSelector createUtilitySelector() {
public NestedLogitSelector createUtilitySelector(Person person, List<DiscreteModeChoiceTrip> tourTrips) {
return new NestedLogitSelector(structure, minimumUtility, maximumUtility);
Copy link
Contributor

Choose a reason for hiding this comment

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

NestedLogitSelector should also get Person and List<DiscreteModeChoiceTrip> and write out utilities.

Copy link
Contributor

@balacmi balacmi left a comment

Choose a reason for hiding this comment

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

Thank you, @Dib-AEK, for these changes; please take a look at my comments.

@sebhoerl sebhoerl changed the title feat: Detailed utilities writer feat(discrete_mode_choice): detailed utilities writer Aug 16, 2025
Copy link
Contributor

@sebhoerl sebhoerl left a comment

Choose a reason for hiding this comment

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

General comment, taking up Milos' comments: It's not really necessary to modify all the UtilitySelector implementations. It would be cleaner to just write a decorator for a UtilitySelector (e.g. WritingUtilitySelector) that takes another UtilitySelector as an argument. Then the custom logic can happen in addCandidate and select and the call is forwarded to the delegate.

In fact, the decorator pattern can already be applied to the factory of the UtilitySelector. So a WritingUtilityFactory can delegate to any other specific implementation.

Also referring to Milos' comment, I think it is better to collect the candidates in each call to addCandidate and then only perform the writing in select.

Copy link
Contributor

@sebhoerl sebhoerl left a comment

Choose a reason for hiding this comment

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

General comment, taking up Milos' comments: It's not really necessary to modify all the UtilitySelector implementations. It would be cleaner to just write a decorator for a UtilitySelector (e.g. WritingUtilitySelector) that takes another UtilitySelector as an argument. Then the custom logic can happen in addCandidate and select and the call is forwarded to the delegate.

In fact, the decorator pattern can already be applied to the factory of the UtilitySelector. So a WritingUtilityFactory can delegate to any other specific implementation.

Also referring to Milos' comment, I think it is better to collect the candidates in each call to addCandidate and then only perform the writing in select.

@sebhoerl
Copy link
Contributor

And please add a unit test.

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.

3 participants