Skip to content

Unify quantitative actions interface#111

Open
shaharbar1 wants to merge 1 commit intodevelopfrom
feature/unified_quantitative_interface
Open

Unify quantitative actions interface#111
shaharbar1 wants to merge 1 commit intodevelopfrom
feature/unified_quantitative_interface

Conversation

@shaharbar1
Copy link
Collaborator

Changes:

  • Moved _normalize_field method to PyBanditsBaseModel for default value normalization of optional fields.
  • Unified quantitative actions interface to use callables for general form, rather than floats for zooming.
  • Improved tests for action selection and quantitative actions, ensuring proper handling of constraints and expected results.

@shaharbar1 shaharbar1 added the enhancement New feature or request label Nov 18, 2025
@shaharbar1 shaharbar1 force-pushed the feature/unified_quantitative_interface branch 5 times, most recently from 4fee41e to c052743 Compare November 18, 2025 11:36
@shaharbar1 shaharbar1 force-pushed the feature/unified_quantitative_interface branch 11 times, most recently from 4669026 to b7e5bec Compare November 25, 2025 12:37
@shaharbar1 shaharbar1 force-pushed the feature/unified_quantitative_interface branch 2 times, most recently from 1664e90 to 5a43378 Compare December 1, 2025 07:10
@shaharbar1 shaharbar1 force-pushed the feature/unified_quantitative_interface branch from 5a43378 to a9d8084 Compare December 25, 2025 07:30
Copy link
Collaborator

@AnastasiiaKabeshova AnastasiiaKabeshova left a comment

Choose a reason for hiding this comment

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

Great code organization improvement :)

Just one question for myself: Why was Optuna replaced? (Performance, dependency reduction, or other reasons?)

return result.x
else:
logger.warning(f"Optimization failed: {result.message}")
return None
Copy link
Collaborator

Choose a reason for hiding this comment

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

When optimization fails, maximize_by_quantity returns None
Did you check that all callers handle None properly?
Whether a more specific exception would be better?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good point, I added an appropriate exception handling.

QuantitativeProbabilityWeight = Tuple[Tuple[Tuple[Float01, ...], ProbabilityWeight], ...]
QuantitativeMOProbability = Tuple[Tuple[Tuple[Float01, ...], List[Probability]], ...]
QuantitativeMOProbabilityWeight = Tuple[Tuple[Tuple[Float01, ...], List[ProbabilityWeight]], ...]
QuantitativeProbability = Callable[[np.ndarray], Probability]
Copy link
Collaborator

Choose a reason for hiding this comment

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

The name of the type is misleading...
maybe it should be: QuantitativeProbabilityFunction?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@ronshiff1 Maybe ContinuousProbability? I just didn't want it to be too long.

@@ -68,6 +78,12 @@ class QuantitativeModel(BaseModelSO, ABC):
def sample_proba(self, **kwargs) -> List[QuantitativeProbability]:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please explain why list. For Multi objective?

Copy link
Collaborator

@ronshiff1 ronshiff1 Jan 15, 2026

Choose a reason for hiding this comment

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

If I understand correctly - it's for different segments?
I think that it is very specific to the zooming algorithm, and it should be function which receives an input, and outputs one function, and not a list
the zooming model should create this function and handle the segmentation internally (I also commented on the first sample_proba implementation)

Copy link
Collaborator Author

@shaharbar1 shaharbar1 Jan 19, 2026

Choose a reason for hiding this comment

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

The list is not because of zooming. It returns a list whose length is as the number of samples.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I fixed the docstring to make it explicit.

@shaharbar1 shaharbar1 force-pushed the feature/unified_quantitative_interface branch from a9d8084 to 7f6a131 Compare January 19, 2026 08:16
for segment in segment_probabilities.keys():
if quantity in segment:
segment_probas_for_segment = segment_probabilities[segment]
return segment_probas_for_segment[sample_idx][output_index] # Probability or weight
Copy link
Collaborator

@ronshiff1 ronshiff1 Jan 19, 2026

Choose a reason for hiding this comment

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

Why not directly return segment_probas_for_segment[sample_idx] which is tuple?
(so def create_probability_or_weight_function(
sample_idx: NonNegativeInt
) -> Tuple[QuantitativeProbability, QuantitativeWeight]:

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Then in the cmab class you would need to separate them to list of probabilities and list of weights.
So I preferred to do it in advance.

return annotation

@classmethod
def _normalize_field(cls, v: Any, field_name: str) -> Any:
Copy link
Collaborator

Choose a reason for hiding this comment

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

why do we need this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I use it in validators of "pre" behavior to get the default value of an ungiven attribute

rewards: Union[List[BinaryReward], List[List[BinaryReward]]]
The reward for each sample.
"""
super()._quantitative_update(quantities, rewards)
Copy link
Collaborator

Choose a reason for hiding this comment

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

this:
quantities: Optional[List[Union[float, List[float], None]]],
rewards: Union[List[BinaryReward], List[List[BinaryReward]]],
doesn't conform with the ZoomingModel _quantitative_update:
def _quantitative_update(self, quantities: List[Union[float, np.ndarray]], rewards: List[BinaryReward], **kwargs):

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Wow! Nice catch. Fixed to: Optional[List[Union[float, List[float], None]]]

@ronshiff1 ronshiff1 self-requested a review February 5, 2026 14:31
### Changes:
* Moved `_normalize_field` method to `PyBanditsBaseModel` for default value normalization of optional fields.
* Unified quantitative actions interface to use callables for general form, rather than floats for zooming.
* Improved tests for action selection and quantitative actions, ensuring proper handling of constraints and expected results.
@shaharbar1 shaharbar1 force-pushed the feature/unified_quantitative_interface branch from 7f6a131 to 2c00401 Compare February 5, 2026 20:52
self,
p: ActionRewardLikelihood,
actions: Optional[Dict[ActionId, BaseModel]] = None,
) -> ActionId:
Copy link
Collaborator

@ronshiff1 ronshiff1 Feb 6, 2026

Choose a reason for hiding this comment

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

say, shouldnt it be
-> Union[ActionId, QuantitativeActionId]?

"\n",
" # Observe reward\n",
" rewards = [\n",
" reward_function(chosen_action, chosen_quantity, current_context[0])\n",
Copy link
Collaborator

Choose a reason for hiding this comment

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

isn't current_context[0] a bug?
it uses the first context for all samples

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

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants