Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Create Artificial Norm Algo within Reduction Workflow #463

Open
wants to merge 33 commits into
base: next
Choose a base branch
from

Conversation

darshdinger
Copy link
Contributor

@darshdinger darshdinger commented Oct 3, 2024

Description of work

The purpose for this PR is the implement the previously created artificial normalization within the Reduction workflow. The changes ensure the workflow can handle missing normalization data by applying artificial normalization and introduce enhancements to the user interface, logging, and data handling processes.

Explanation of work

Key updates include:

  1. Artificial Normalization:
  • Added the ArtificialNormalizationRecipe to GenericRecipe that uses the CreateArtificialNormalizationAlgo to handle cases when normalization data is missing.
  • Automatically extracts parameters and queues the algorithm to create an artificial normalization workspace when needed.
  1. Workflow Updates:
  • The Reduction workflow can now dynamically adjust for missing calibration or normalization data, applying artificial normalization when necessary.
  • Updated the UI to prompt users for artificial normalization inputs and seamlessly continue the workflow.
  1. Error Handling and Logging:
  • Improved logging and error messages to guide users through situations where data is missing.
  • Enhanced exception handling to gracefully manage missing states or permissions.
  1. Backend Improvements:
  • Refactored the ReductionService to handle normalization and calibration more flexibly.
  • Expanded unit tests to cover new cases with artificial normalization.

To test

Insure all pytests pass. Please launch SNAPRed UI with a fresh (no states initialized) local copy of Calibration_next specified within application.yml. First run the Diffraction Calibration with the following inputs:

  • Run Number: 58882
  • Convergence Threshold: 0.1
  • Bins Across Peak Width: 10
  • Sample: Silicon_NIST_640D_001.json
  • Grouping File: Column

Create a new state when prompted. Then continue on through workflow through successful completion.
Now moving onto the Reduction tab, notice there is a new tab called "Artificial Normalization". Press the Continue button and notice the UI handling for no inputs:

image

Finally, without running a normalization, run the Reduction workflow with the following inputs:

  • Run Number: 58882

Continue the workflow to notice the following message pop-up:

image

Continue Past the possible warning for permissions check but clicking Yes. Find that the workflow completes successfully.

Finally, re-run this but with a normalization this time and see that the Artificial Normalization flow is not used in this case.

Dev testing

Insure all pytests pass

CIS testing

See above.

Link to EWM item

EWM # 7123

&

EWM # 7121

Verification

  • the author has read the EWM story and acceptance critera
  • the reviewer has read the EWM story and acceptance criteria
  • the reviewer certifies the acceptance criteria below reflect the criteria in EWM

Acceptance Criteria

This list is for ease of reference, and does not replace reading the EWM story as part of the review. Verify this list matches the EWM story before reviewing.

  • Note - No acceptance criteria is provided for this story. This recipe shall be fully testable within the acceptance criteria of the UI portion of this EPIC.

@darshdinger darshdinger marked this pull request as ready for review October 7, 2024 13:23
@neutrons neutrons deleted a comment from codecov bot Oct 14, 2024
@neutrons neutrons deleted a comment from codecov bot Oct 14, 2024
@neutrons neutrons deleted a comment from codecov bot Oct 14, 2024
@neutrons neutrons deleted a comment from codecov bot Oct 14, 2024
@neutrons neutrons deleted a comment from codecov bot Oct 14, 2024
@neutrons neutrons deleted a comment from codecov bot Oct 14, 2024
@neutrons neutrons deleted a comment from codecov bot Oct 14, 2024
@neutrons neutrons deleted a comment from codecov bot Oct 14, 2024
@neutrons neutrons deleted a comment from codecov bot Oct 14, 2024
Copy link

codecov bot commented Oct 14, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 96.56%. Comparing base (205b119) to head (3942382).

Additional details and impacted files
@@            Coverage Diff             @@
##             next     #463      +/-   ##
==========================================
+ Coverage   96.53%   96.56%   +0.02%     
==========================================
  Files          63       63              
  Lines        4503     4541      +38     
==========================================
+ Hits         4347     4385      +38     
  Misses        156      156              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

class ArtificialNormResponse(BaseModel):
diffractionWorkspace: WorkspaceName

model_config = ConfigDict(
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should probably pick one or the other format for specifying model configs.
I like the class based one here but I think we use the above more often in the code

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. Updated

tb = exception.__traceback__
def __init__(self, exception):
# Handle both string and Exception types for 'exception'
if isinstance(exception, Exception):
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not sure we should be expanding exceptions type to be both Exception and str, I dont think I see the usecase in this pr? I just see this used in a test file.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is used in the test.

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 has been reverted

Comment on lines +98 to +158
if request.artificialNormalization is not None:
continueFlags = ContinueWarning.Type.UNSET

# check that the user has write permissions to the save directory
if not self.checkWritePermissions(request.runNumber):
continueFlags |= ContinueWarning.Type.NO_WRITE_PERMISSIONS

# remove any continue flags that are present in the request by xor-ing with the flags
if request.continueFlags:
continueFlags = continueFlags ^ (request.continueFlags & continueFlags)

if continueFlags:
raise ContinueWarning(
f"<p>Remeber, you don't have permissions to write to "
f"<br><b>{self.getSavePath(request.runNumber)}</b>,<br>"
+ "but you can still save using the workbench tools.</p>"
+ "<p>Would you like to continue anyway?</p>",
continueFlags,
)
else:
continueFlags = ContinueWarning.Type.UNSET
warningMessages = []

# check if a normalization is present
if not self.dataFactoryService.normalizationExists(request.runNumber, request.useLiteMode):
continueFlags |= ContinueWarning.Type.MISSING_NORMALIZATION
warningMessages.append("Normalization is missing, continuing with artificial normalization step.")
# check if a diffraction calibration is present
if not self.dataFactoryService.calibrationExists(request.runNumber, request.useLiteMode):
continueFlags |= ContinueWarning.Type.MISSING_DIFFRACTION_CALIBRATION
warningMessages.append("Diffraction calibration is missing, continuing with uncalibrated mode.")

# remove any continue flags that are present in the request by xor-ing with the flags
if request.continueFlags:
continueFlags = continueFlags ^ (request.continueFlags & continueFlags)

if continueFlags:
detailedMessage = "\n".join(warningMessages)
raise ContinueWarning(
f"The reduction cannot proceed due to missing data:\n{detailedMessage}\n", continueFlags
)

# ... ensure separate continue warnings ...
continueFlags = ContinueWarning.Type.UNSET
# ... ensure separate continue warnings ...
continueFlags = ContinueWarning.Type.UNSET

# check that the user has write permissions to the save directory
if not self.checkWritePermissions(request.runNumber):
continueFlags |= ContinueWarning.Type.NO_WRITE_PERMISSIONS
# check that the user has write permissions to the save directory
if not self.checkWritePermissions(request.runNumber):
continueFlags |= ContinueWarning.Type.NO_WRITE_PERMISSIONS

# remove any continue flags that are present in the request by xor-ing with the flags
if request.continueFlags:
continueFlags = continueFlags ^ (request.continueFlags & continueFlags)
# remove any continue flags that are present in the request by xor-ing with the flags
if request.continueFlags:
continueFlags = continueFlags ^ (request.continueFlags & continueFlags)

if continueFlags:
raise ContinueWarning(
f"<p>It looks like you don't have permissions to write to "
f"<br><b>{self.getSavePath(request.runNumber)}</b>,<br>"
+ "but you can still save using the workbench tools.</p>"
+ "<p>Would you like to continue anyway?</p>",
continueFlags,
)
if continueFlags:
raise ContinueWarning(
f"<p>It looks like you don't have permissions to write to "
f"<br><b>{self.getSavePath(request.runNumber)}</b>,<br>"
+ "but you can still save using the workbench tools.</p>"
+ "<p>Would you like to continue anyway?</p>",
continueFlags,
)
Copy link
Collaborator

Choose a reason for hiding this comment

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

This and this are duplicate code. Please break this method out into smaller sub methods to reduce its complexity and the amount of nesting.
i.e. this should be its own method, and this should be another method, and those two should be substituted into the above method like so(I reorged it so we didnt need to check write perms twice):

if request.artificialNormalization is None:
    self._validateCalibrationAndNormalizationExists(request)

self._validateWritePermissions(request)

Comment on lines 174 to +176
groceries = self.fetchReductionGroceries(request)
if isinstance(groceries, ArtificialNormResponse):
return groceries
Copy link
Collaborator

Choose a reason for hiding this comment

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

Again we shouldnt be expanding type like this. I'll probably have another comment when I get to prepReductionIngredients.

Comment on lines +399 to +406
calVersion = None
normVersion = None
calVersion = self.dataFactoryService.getThisOrLatestCalibrationVersion(
request.runNumber, request.useLiteMode
)
self.groceryClerk.name("diffcalWorkspace").diffcal_table(request.runNumber, calVersion).useLiteMode(
request.useLiteMode
).add()
Copy link
Collaborator

Choose a reason for hiding this comment

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

this is very similar code to the above, I dont think normVersion is used in either of the highlighted snippets, so we can extract this duplicate code out and above the if/else statement.

self._artificialNormalizationView.updateWorkspaces(diffractionWorkspace, artificialNormWorkspace)
else:
print(f"Error: Workspaces not found in the response: {response.data}")
except Exception as e: # noqa: BLE001
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please dont swallow errors like this, let it fail. Its much harder to track down without a stacktrace, or rewrap it with a human readable error message and rethrow.

@@ -149,7 +162,7 @@ def _triggerReduction(self, workflowPresenter):
)

response = self.request(path="reduction/", payload=request_)
if response.code == ResponseCode.OK:
if isinstance(response.data, ReductionResponse):
Copy link
Collaborator

Choose a reason for hiding this comment

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

If I am understanding this correctly, you:

  1. Attempt reduction
  2. If it turns out you need an artificial normalization(As determined by the response type containing one workspace name) then you
  3. Create the base Artificial Normalization workspace for the next step
  4. Allow the user to tweak the AN
  5. Proceed with Reduction

I think it would be more straight forward if you didnt shoehorn the check into the same method as reduction.

  1. User inputs values in first view -> method to check if there needs to be an AN, create one if necessary, update/skip AN view
  2. No AN Required? The AN view is either skipped entirely or greyed out with a message letting the user know no AN was required.
  3. Yes AN Required? The AN is initialized and the user tweaks it in the AN view
  4. The AN Tweak View is utilized or skipped -> Proceed with Reduction
  5. Save.

This way you only ever call reduction in one spot and you have a linear non branching chain of events. It should reduce the complexity of the code.

Copy link
Collaborator

Choose a reason for hiding this comment

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

(You would also be rid of the isinstance branching, and can utilize typical response procedure)

Copy link
Collaborator

@walshmm walshmm Oct 17, 2024

Choose a reason for hiding this comment

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

According to the acceptance criteria of the parent story

The user can select "artificial normalisation" via a UI prompt and submit the two parameters above.

Unfortunately Im not sure it was supposed to be a step in reduction 😔, please check with Malcolm to see if this is acceptable or if you need to move it.

# Update the view with new workspaces
self._artificialNormalizationView.updateWorkspaces(diffractionWorkspace, artificialNormWorkspace)

except Exception as e: # noqa: BLE001
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same as above, dont swallow all exceptions arbitrarily, this will be a massive headache to someone.

@walshmm
Copy link
Collaborator

walshmm commented Oct 17, 2024

Please reference the base Story 6833 for the full scope of Acceptance Criteria

# Note that the run number is deliberately not deleted from the run numbers list.
# Almost certainly it should be moved to a "completed run numbers" list.

# SPECIAL FOR THE REDUCTION WORKFLOW: clear everything _except_ the output workspaces
# _before_ transitioning to the "save" panel.
# TODO: make '_clearWorkspaces' a public method (i.e make this combination a special `cleanup` method).
self._clearWorkspaces(exclude=self.outputs, clearCachedWorkspaces=True)
workflowPresenter.advanceWorkflow()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Disable ArtificialNormView here as well.

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.

2 participants