Skip to content

Add bounded and inequality operand definition#53

Merged
HarrisonKramer merged 11 commits intoHarrisonKramer:masterfrom
drpaprika:operands
Jan 31, 2025
Merged

Add bounded and inequality operand definition#53
HarrisonKramer merged 11 commits intoHarrisonKramer:masterfrom
drpaprika:operands

Conversation

@drpaprika
Copy link
Collaborator

This PR introduces an implementation of a very usefull feature : bounded and inequality operands.
Up until now, an operand could only be defined with a target argument.

problem.add_operand() now accepts either a target, bounds or inequality bounds more_than and less_than. Checks are done to ensure that conflicting parameters cannot be set at the same time. Specifying both more_than and less_than has the same effect as specifying bounds.

I also fixed tests that were not passing (but I didn't add any for this new feature), all tests should now pass.

Please review carefully before merging, as this is a critical optimization feature.

Nb: Would you be so kind as to give me the permissions to Optiland's repo ? I could push directly without going through a fork and contributing to other PRs would be easier. Thanks!

@codecov
Copy link

codecov bot commented Jan 30, 2025

Codecov Report

Attention: Patch coverage is 79.54545% with 9 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
optiland/optimization/operand/operand.py 74.19% 8 Missing ⚠️
optiland/optimization/operand/ray.py 50.00% 1 Missing ⚠️
Flag Coverage Δ
unittests 97.22% <79.54%> (+0.05%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
optiland/optimization/operand/operand_manager.py 100.00% <100.00%> (ø)
optiland/optimization/optimization.py 100.00% <100.00%> (ø)
optiland/surfaces/surface_factory.py 100.00% <100.00%> (ø)
optiland/tolerancing/core.py 100.00% <100.00%> (ø)
optiland/optimization/operand/ray.py 98.75% <50.00%> (+14.13%) ⬆️
optiland/optimization/operand/operand.py 86.20% <74.19%> (-13.80%) ⬇️

... and 1 file with indirect coverage changes

@HarrisonKramer
Copy link
Owner

Thanks for the addition! This will be very useful. Will review shortly.

Just granted you access. Please kindly continue to use PRs for all changes, unless something really minor.

Copy link
Owner

@HarrisonKramer HarrisonKramer left a comment

Choose a reason for hiding this comment

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

This is an impressive addition and extremely useful.

Only one critical remark:

  • I propose to use only min_val and max_val in operand.py to define bounds. The bounds argument appears to be redundant in this case, but please correct me if I miss some logic. I think this would simplify the logic of that dataclass and the downstream code.

I will definitely want to add quite a few tests to cover the new functionalities, as well as many new examples for the docs, but those are not blocking. Everything looks logical and existing tests pass. I can add more docs/tests later on.

Thanks again! let me know if you want a hand with any changes, or have other remarks.

Kramer

Copy link
Owner

Choose a reason for hiding this comment

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

These details are not critical at all, but consider:

  • run the optimization with a high tol to get a better final result e.g., res = optimizer.optimize(tol=1e-9)
  • no big deal, but the image height operands are still pretty far off after optimization. I'm curious if there's any fix for that e.g., increasing weights or changing starting point. Again, not critical and no real need to change.

target (float): The target value for the operand.
operand_type (str): The type of the operand.
target (float): The target value of the operand.
bounds (list): The operand should stay between the bounds (bounded operand).
Copy link
Owner

Choose a reason for hiding this comment

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

  • what do you think about using min_val and max_val instead of more_than and less_than? These would then be the same as those used in the variables (also min_val and max_val) and it is more intuitive (at least for me).
  • Do we need both bounds and the min_val/max_val? Perhaps I miss some of the logic, but would it be enough only to use min_val and max_val? They define bounds too, after all. Unless this breaks something I'm missing, I strongly advise making this change and propagating it through the rest of this file. (and other files).

self.operands = []

def add(self, operand_type, target, weight=1, input_data={}):
def add(self, operand_type=None, target=None, bounds=None, more_than=None, less_than=None, weight=1, input_data={}):
Copy link
Owner

Choose a reason for hiding this comment

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

if you agree with the change to operand.py, then you can update these arguments

for op in self.operands],
'Target': [op.target for op in self.operands],
'Target': [f'{op.target:+.3f}' if op.target is not None else '' for op in self.operands],
'Bounds': [op.bounds if op.bounds else '' for op in self.operands],
Copy link
Owner

Choose a reason for hiding this comment

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

if you agree with changing bounds argument in operand.py, then consider using 'Min. Bound' and 'Max. Bound' as key names, which matches those used for variables.

@drpaprika
Copy link
Collaborator Author

Thanks for the access! You're right I implemented the changes, the class is simpler now

Copy link
Owner

@HarrisonKramer HarrisonKramer left a comment

Choose a reason for hiding this comment

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

Looks solid!

I think you can simplify the logic of Operand.delta_ineq slightly (see comment), then should be good to merge.

then this operand simply is zero.
"""

# One of the two
Copy link
Owner

Choose a reason for hiding this comment

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

I don't think you need 3 conditions. Lines 198-208 can be simplified into the following (you can confirm):

lower_penalty = max(0, self.min_val - self.value) if self.min_val is not None else 0
upper_penalty = max(0, self.value - self.max_val) if self.max_val is not None else 0
return lower_penalty + upper_penalty

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It seems to work thanks!

Copy link
Owner

@HarrisonKramer HarrisonKramer left a comment

Choose a reason for hiding this comment

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

Looks good! We can merge.

@HarrisonKramer HarrisonKramer merged commit df4ea02 into HarrisonKramer:master Jan 31, 2025
4 of 5 checks passed
@drpaprika drpaprika deleted the operands branch January 31, 2025 16:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants