From a08b078dcf2308884e8e030f849dc5b5f42d0264 Mon Sep 17 00:00:00 2001 From: jbphet Date: Tue, 12 Nov 2024 17:03:56 -0700 Subject: [PATCH] handle unpolarized setting in polarization angle indicators, see https://github.com/phetsims/quantum-measurement/issues/52 --- js/QuantumMeasurementStrings.ts | 1 + js/photons/model/Laser.ts | 8 ++-- .../view/FlatPolarizationAngleIndicator.ts | 37 ++++++++++----- .../view/ObliquePolarizationAngleIndicator.ts | 47 ++++++++++++------- .../view/PhotonDetectionProbabilityPanel.ts | 30 ++++++++---- quantum-measurement-strings_en.json | 3 ++ 6 files changed, 87 insertions(+), 39 deletions(-) diff --git a/js/QuantumMeasurementStrings.ts b/js/QuantumMeasurementStrings.ts index 2a5b410..240be92 100644 --- a/js/QuantumMeasurementStrings.ts +++ b/js/QuantumMeasurementStrings.ts @@ -77,6 +77,7 @@ type StringsType = { 'blochSphereStringProperty': LocalizedStringProperty; 'representationStringProperty': LocalizedStringProperty; 'unpolarizedStringProperty': LocalizedStringProperty; + 'unknownProbabilitySymbolStringProperty': LocalizedStringProperty; 'zProjectionStringProperty': LocalizedStringProperty; 'xProjectionStringProperty': LocalizedStringProperty; }; diff --git a/js/photons/model/Laser.ts b/js/photons/model/Laser.ts index 4d39a40..51b5e98 100644 --- a/js/photons/model/Laser.ts +++ b/js/photons/model/Laser.ts @@ -66,9 +66,10 @@ export default class Laser { // The custom polarization angle for the emitted photons. This is only used when the preset direction is "custom". public readonly customPolarizationAngleProperty: NumberProperty; - // The polarization angle that is used for emission. This is a derived - and thus read-only - property that is - // derived from the preset polarization direction and the custom polarization angle. - public readonly polarizationAngleProperty: TReadOnlyProperty; + // The polarization angle of the emitted photons. This is a derived - and thus read-only - property that is + // derived from the preset polarization direction and the custom polarization angle. A value of null indicates that + // the emitted photons are unpolarized, meaining that their individual polarization angles are random. + public readonly polarizationAngleProperty: TReadOnlyProperty; // The set of photons that are used for emission. This is a reference to the same array that is used in the scene model. private readonly photons: Photon[]; @@ -108,6 +109,7 @@ export default class Laser { return presetPolarizationDirection === 'vertical' ? 90 : presetPolarizationDirection === 'horizontal' ? 0 : presetPolarizationDirection === 'fortyFiveDegrees' ? 45 : + presetPolarizationDirection === 'unpolarized' ? null : customPolarizationAngle; } ); diff --git a/js/photons/view/FlatPolarizationAngleIndicator.ts b/js/photons/view/FlatPolarizationAngleIndicator.ts index 0b76bcd..cdea0ae 100644 --- a/js/photons/view/FlatPolarizationAngleIndicator.ts +++ b/js/photons/view/FlatPolarizationAngleIndicator.ts @@ -7,6 +7,7 @@ * @author John Blanco (PhET Interactive Simulations) */ +import DerivedProperty from '../../../../axon/js/DerivedProperty.js'; import TReadOnlyProperty from '../../../../axon/js/TReadOnlyProperty.js'; import Bounds2 from '../../../../dot/js/Bounds2.js'; import Utils from '../../../../dot/js/Utils.js'; @@ -44,7 +45,7 @@ const UNIT_LENGTH = AXIS_LENGTH * 0.75; export default class FlatPolarizationAngleIndicator extends Node { - public constructor( polarizationAngleProperty: TReadOnlyProperty, + public constructor( polarizationAngleProperty: TReadOnlyProperty, providedOptions?: PolarizationPlaneRepresentationOptions ) { // Create the vertical axis. @@ -69,9 +70,17 @@ export default class FlatPolarizationAngleIndicator extends Node { centerY: horizontalAxis.centerY } ); + // Create a Property for the fill used for the unit circle, since it changes when the photons are unpolarized. + const unitCircleFillProperty = new DerivedProperty( + [ polarizationAngleProperty ], + polarizationAngle => polarizationAngle === null ? + QuantumMeasurementColors.photonBaseColorProperty.value : + Color.LIGHT_GRAY + ); + // Create a unit circle. const unitCircle = new Circle( UNIT_LENGTH, { - fill: Color.GRAY, + fill: unitCircleFillProperty, opacity: 0.3 } ); @@ -109,16 +118,22 @@ export default class FlatPolarizationAngleIndicator extends Node { // Update the positions of the polarization vectors as the polarization angle changes. polarizationAngleProperty.link( polarizationAngle => { - // Calculate the positions for the two ends of the polarization vector. - const polarizationVectorTipPosition = new Vector2( - Math.cos( -Utils.toRadians( polarizationAngle ) ), - Math.sin( -Utils.toRadians( polarizationAngle ) ) - ).times( UNIT_LENGTH ); - const polarizationVectorTailPosition = polarizationVectorTipPosition.times( -1 ); + // Only show the polarization vector if the angle is not null. + polarizationVectorNode.visible = polarizationAngle !== null; + + if ( polarizationAngle !== null ) { + + // Calculate the positions for the two ends of the polarization vector. + const polarizationVectorTipPosition = new Vector2( + Math.cos( -Utils.toRadians( polarizationAngle ) ), + Math.sin( -Utils.toRadians( polarizationAngle ) ) + ).times( UNIT_LENGTH ); + const polarizationVectorTailPosition = polarizationVectorTipPosition.times( -1 ); - // Project the vectors and set the tips of the arrows accordingly. - polarizationVectorNode.setTip( polarizationVectorTipPosition.x, polarizationVectorTipPosition.y ); - polarizationVectorNode.setTail( polarizationVectorTailPosition.x, polarizationVectorTailPosition.y ); + // Project the vectors and set the tips of the arrows accordingly. + polarizationVectorNode.setTip( polarizationVectorTipPosition.x, polarizationVectorTipPosition.y ); + polarizationVectorNode.setTail( polarizationVectorTailPosition.x, polarizationVectorTailPosition.y ); + } } ); } } diff --git a/js/photons/view/ObliquePolarizationAngleIndicator.ts b/js/photons/view/ObliquePolarizationAngleIndicator.ts index 4833672..63ff9ff 100644 --- a/js/photons/view/ObliquePolarizationAngleIndicator.ts +++ b/js/photons/view/ObliquePolarizationAngleIndicator.ts @@ -20,6 +20,7 @@ * @author John Blanco (PhET Interactive Simulations) */ +import DerivedProperty from '../../../../axon/js/DerivedProperty.js'; import TReadOnlyProperty from '../../../../axon/js/TReadOnlyProperty.js'; import Bounds2 from '../../../../dot/js/Bounds2.js'; import Utils from '../../../../dot/js/Utils.js'; @@ -69,7 +70,7 @@ const project3Dto2D = ( x: number, y: number, z: number ): Vector2 => { export default class ObliquePolarizationAngleIndicator extends Node { - public constructor( polarizationAngleProperty: TReadOnlyProperty, + public constructor( polarizationAngleProperty: TReadOnlyProperty, providedOptions?: PolarizationPlaneRepresentationOptions ) { // Create the Y axis line with an arrow head. This is pointing directly to the right. We have to do this as a @@ -114,6 +115,14 @@ export default class ObliquePolarizationAngleIndicator extends Node { center: xAxisTipPosition.times( 1.4 ) } ); + // Create a Property for the fill used for the unit circle, since it changes when the photons are unpolarized. + const unitCircleFillProperty = new DerivedProperty( + [ polarizationAngleProperty ], + polarizationAngle => polarizationAngle === null ? + QuantumMeasurementColors.photonBaseColorProperty.value : + Color.LIGHT_GRAY + ); + // Create a unit circle that is projected into the x-z plane. const numberOfPoints = 100; const projectedStartingPoint = project3Dto2D( UNIT_LENGTH, 0, 0 ); @@ -128,7 +137,7 @@ export default class ObliquePolarizationAngleIndicator extends Node { segmentedEllipseShape.lineToPoint( projectedStartingPoint ); segmentedEllipseShape.close(); const projectedXZUnitCircle = new Path( segmentedEllipseShape, { - fill: Color.GRAY, + fill: unitCircleFillProperty, opacity: 0.3 } ); @@ -142,7 +151,7 @@ export default class ObliquePolarizationAngleIndicator extends Node { fill: '#0f0', doubleHead: true }; - const polarizationVector = new ArrowNode( 0, AXIS_LENGTH, 0, -AXIS_LENGTH, polarizationVectorOptions ); + const polarizationVectorNode = new ArrowNode( 0, AXIS_LENGTH, 0, -AXIS_LENGTH, polarizationVectorOptions ); const options = optionize()( { @@ -155,7 +164,7 @@ export default class ObliquePolarizationAngleIndicator extends Node { zAxis, xAxisLabel, zAxisLabel, - polarizationVector, + polarizationVectorNode, yAxisArrowHead, yAxisLine, yAxisLabel @@ -167,18 +176,24 @@ export default class ObliquePolarizationAngleIndicator extends Node { // Update the positions of the polarization vectors as the polarization angle changes. polarizationAngleProperty.link( polarizationAngle => { - // Calculate the position of the polarization unit vector in the x-z plane. - const polarizationVectorPlusInXZPlane = new Vector2( - Math.cos( -Utils.toRadians( polarizationAngle ) ), - Math.sin( -Utils.toRadians( polarizationAngle ) ) - ).times( UNIT_LENGTH ); - const polarizationVectorMinusInXZPlane = polarizationVectorPlusInXZPlane.times( -1 ); - - // Project the vectors and set the tips of the arrows accordingly. - const tip = project3Dto2D( polarizationVectorPlusInXZPlane.x, 0, polarizationVectorPlusInXZPlane.y ); - polarizationVector.setTip( tip.x, tip.y ); - const tail = project3Dto2D( polarizationVectorMinusInXZPlane.x, 0, polarizationVectorMinusInXZPlane.y ); - polarizationVector.setTail( tail.x, tail.y ); + // Only show the polarization vector if the angle is not null. + polarizationVectorNode.visible = polarizationAngle !== null; + + if ( polarizationAngle !== null ) { + + // Calculate the position of the polarization unit vector in the x-z plane. + const polarizationVectorPlusInXZPlane = new Vector2( + Math.cos( -Utils.toRadians( polarizationAngle ) ), + Math.sin( -Utils.toRadians( polarizationAngle ) ) + ).times( UNIT_LENGTH ); + const polarizationVectorMinusInXZPlane = polarizationVectorPlusInXZPlane.times( -1 ); + + // Project the vectors and set the tips of the arrows accordingly. + const tip = project3Dto2D( polarizationVectorPlusInXZPlane.x, 0, polarizationVectorPlusInXZPlane.y ); + polarizationVectorNode.setTip( tip.x, tip.y ); + const tail = project3Dto2D( polarizationVectorMinusInXZPlane.x, 0, polarizationVectorMinusInXZPlane.y ); + polarizationVectorNode.setTail( tail.x, tail.y ); + } } ); } } diff --git a/js/photons/view/PhotonDetectionProbabilityPanel.ts b/js/photons/view/PhotonDetectionProbabilityPanel.ts index 8d3c1bb..774b2a1 100644 --- a/js/photons/view/PhotonDetectionProbabilityPanel.ts +++ b/js/photons/view/PhotonDetectionProbabilityPanel.ts @@ -28,7 +28,7 @@ const BOLD_FONT = new PhetFont( { size: FONT_SIZE, weight: 'bold' } ); export default class PhotonDetectionProbabilityPanel extends Panel { - public constructor( polarizationAngleProperty: TReadOnlyProperty, + public constructor( polarizationAngleProperty: TReadOnlyProperty, providedOptions: PhotonDetectionProbabilityPanelOptions ) { const options = optionize()( { @@ -36,16 +36,18 @@ export default class PhotonDetectionProbabilityPanel extends Panel { stroke: null }, providedOptions ); - // Calculate the probability of a photon being detected as horizontally polarized. + // Calculate the probability of a photon being detected as horizontally polarized. A null value indicates that the + // probability is unknown. const probabilityOfHorizontalProperty = new DerivedProperty( [ polarizationAngleProperty ], - polarizationAngle => Math.cos( Utils.toRadians( polarizationAngle ) ) ** 2 + polarizationAngle => polarizationAngle === null ? null : Math.cos( Utils.toRadians( polarizationAngle ) ) ** 2 ); - // Calculate the probability of a photon being detected as vertically polarized. + // Calculate the probability of a photon being detected as vertically polarized. A null value indicates that the + // probability is unknown. const probabilityOfVerticalProperty = new DerivedProperty( [ probabilityOfHorizontalProperty ], - probabilityOfHorizontal => 1 - probabilityOfHorizontal + probabilityOfHorizontal => probabilityOfHorizontal === null ? null : 1 - probabilityOfHorizontal ); // Create the string Properties that will be displayed in the panel. @@ -54,10 +56,15 @@ export default class PhotonDetectionProbabilityPanel extends Panel { probabilityOfHorizontalProperty, QuantumMeasurementStrings.PStringProperty, QuantumMeasurementStrings.HStringProperty, + QuantumMeasurementStrings.unknownProbabilitySymbolStringProperty, QuantumMeasurementColors.horizontalPolarizationColorProperty ], - ( probabilityOfHorizontal, pString, hString, horizontalColor ) => { - return `${pString}(${getColoredString( hString, horizontalColor )}) = ${Utils.toFixed( probabilityOfHorizontal, 2 )}`; + ( probabilityOfHorizontal, pString, hString, unknownProbabilitySymbol, horizontalColor ) => { + const leftSide = `${pString}(${getColoredString( hString, horizontalColor )})`; + const rightSide = probabilityOfHorizontal === null ? + unknownProbabilitySymbol : + Utils.toFixed( probabilityOfHorizontal, 2 ); + return `${leftSide} = ${rightSide}`; } ); const probabilityOfVerticalStringProperty = new DerivedProperty( @@ -65,10 +72,15 @@ export default class PhotonDetectionProbabilityPanel extends Panel { probabilityOfVerticalProperty, QuantumMeasurementStrings.PStringProperty, QuantumMeasurementStrings.VStringProperty, + QuantumMeasurementStrings.unknownProbabilitySymbolStringProperty, QuantumMeasurementColors.verticalPolarizationColorProperty ], - ( probabilityOfVertical, pString, vString, verticalColor ) => { - return `${pString}(${getColoredString( vString, verticalColor )}) = ${Utils.toFixed( probabilityOfVertical, 2 )}`; + ( probabilityOfVertical, pString, vString, unknownProbabilitySymbol, verticalColor ) => { + const leftSide = `${pString}(${getColoredString( vString, verticalColor )})`; + const rightSide = probabilityOfVertical === null ? + unknownProbabilitySymbol : + Utils.toFixed( probabilityOfVertical, 2 ); + return `${leftSide} = ${rightSide}`; } ); diff --git a/quantum-measurement-strings_en.json b/quantum-measurement-strings_en.json index 630c78a..39d1eec 100644 --- a/quantum-measurement-strings_en.json +++ b/quantum-measurement-strings_en.json @@ -182,6 +182,9 @@ "unpolarized": { "value": "Unpolarized" }, + "unknownProbabilitySymbol": { + "value": "?" + }, "zProjection": { "value": "Z Projection" },