From 974c1b0965b6c40c63019d615783331767f18e8d Mon Sep 17 00:00:00 2001 From: AgustinVallejo Date: Tue, 15 Oct 2024 12:42:05 -0500 Subject: [PATCH] Logic for the SG model, see https://github.com/phetsims/quantum-measurement/issues/53 --- js/spin/model/SpinExperiment.ts | 16 ++++----- js/spin/model/SpinModel.ts | 51 +++++++++++++++++++++++++---- js/spin/model/SternGerlachModel.ts | 36 ++++++++++++++++++++ js/spin/view/ParticleRayPath.ts | 14 +------- js/spin/view/SpinMeasurementArea.ts | 30 ++++++++++++++++- js/spin/view/SpinScreenView.ts | 1 + js/spin/view/SternGerlachNode.ts | 2 +- 7 files changed, 120 insertions(+), 30 deletions(-) diff --git a/js/spin/model/SpinExperiment.ts b/js/spin/model/SpinExperiment.ts index 247dd9a..778b92d 100644 --- a/js/spin/model/SpinExperiment.ts +++ b/js/spin/model/SpinExperiment.ts @@ -14,7 +14,7 @@ import EnumerationValue from '../../../../phet-core/js/EnumerationValue.js'; import quantumMeasurement from '../../quantumMeasurement.js'; -type SternGerlachOptions = { +type SternGerlachExperimentSetting = { isZOriented: boolean; active: boolean; }; @@ -27,22 +27,22 @@ export default class SpinExperiment extends EnumerationValue { public static readonly EXPERIMENT_2 = new SpinExperiment( 'Experiment 2 [SGx]', [ { isZOriented: false, active: true } ] ); - public static readonly EXPERIMENT_3 = new SpinExperiment( 'Experiment 3 [Sz, Sx]', [ + public static readonly EXPERIMENT_3 = new SpinExperiment( 'Experiment 3 [SGz, SGx]', [ { isZOriented: true, active: true }, { isZOriented: false, active: true }, { isZOriented: false, active: true } ] ); - public static readonly EXPERIMENT_4 = new SpinExperiment( 'Experiment 4 [Sz, Sz]', [ + public static readonly EXPERIMENT_4 = new SpinExperiment( 'Experiment 4 [SGz, SGz]', [ { isZOriented: true, active: true }, { isZOriented: true, active: true }, { isZOriented: true, active: true } ] ); - public static readonly EXPERIMENT_5 = new SpinExperiment( 'Experiment 5 [Sx, Sz]', [ + public static readonly EXPERIMENT_5 = new SpinExperiment( 'Experiment 5 [SGx, SGz]', [ { isZOriented: false, active: true }, { isZOriented: true, active: true }, { isZOriented: true, active: true } ] ); - public static readonly EXPERIMENT_6 = new SpinExperiment( 'Experiment 6 [Sx, Sx]', [ + public static readonly EXPERIMENT_6 = new SpinExperiment( 'Experiment 6 [SGx, SGx]', [ { isZOriented: false, active: true }, { isZOriented: false, active: true }, { isZOriented: false, active: true } @@ -57,12 +57,12 @@ export default class SpinExperiment extends EnumerationValue { public readonly experimentName: string | TReadOnlyProperty; - public readonly experimentSettings: SternGerlachOptions[]; + public readonly experimentSetting: SternGerlachExperimentSetting[]; - public constructor( experimentName: string | TReadOnlyProperty, experimentSettings: SternGerlachOptions[] ) { + public constructor( experimentName: string | TReadOnlyProperty, experimentSetting: SternGerlachExperimentSetting[] ) { super(); this.experimentName = experimentName; - this.experimentSettings = experimentSettings; + this.experimentSetting = experimentSetting; } } diff --git a/js/spin/model/SpinModel.ts b/js/spin/model/SpinModel.ts index 0a1b118..dda5d00 100644 --- a/js/spin/model/SpinModel.ts +++ b/js/spin/model/SpinModel.ts @@ -10,6 +10,7 @@ * @author Agustín Vallejo */ +import Emitter from '../../../../axon/js/Emitter.js'; import NumberProperty from '../../../../axon/js/NumberProperty.js'; import Property from '../../../../axon/js/Property.js'; import TReadOnlyProperty from '../../../../axon/js/TReadOnlyProperty.js'; @@ -49,13 +50,13 @@ export class SpinValue extends EnumerationValue { } export class SourceMode extends EnumerationValue { - + public static readonly SINGLE = new SourceMode( 'Single Particle', 'singleParticle' ); - + public static readonly CONTINUOUS = new SourceMode( 'Continuous', 'continuous' ); - + public static readonly enumeration = new Enumeration( SourceMode ); - + public constructor( public readonly sourceName: string | TReadOnlyProperty, public readonly tandemName: string ) { super(); } @@ -78,6 +79,10 @@ export default class SpinModel implements TModel { public readonly particleAmmountProperty: NumberProperty; + // Used to update the opacities of the particle rays + // TODO: Better naming?? https://github.com/phetsims/quantum-measurement/issues/53 + public readonly probabilitiesUpdatedEmitter: Emitter = new Emitter(); + public constructor( providedOptions: QuantumMeasurementModelOptions ) { this.sourceModeProperty = new Property( SourceMode.SINGLE ); @@ -97,15 +102,24 @@ export default class SpinModel implements TModel { this.currentExperimentProperty.link( experiment => { sternGerlachModels.forEach( ( sternGerlachModel, index ) => { - if ( experiment.experimentSettings.length > index ) { + if ( experiment.experimentSetting.length > index ) { // TODO: Should visibility be only handled via the View? https://github.com/phetsims/quantum-measurement/issues/53 - sternGerlachModel.isVisibleProperty.set( experiment.experimentSettings[ index ].active ); - sternGerlachModel.isZOrientedProperty.set( experiment.experimentSettings[ index ].isZOriented ); + sternGerlachModel.isVisibleProperty.set( experiment.experimentSetting[ index ].active ); + sternGerlachModel.isZOrientedProperty.set( experiment.experimentSetting[ index ].isZOriented ); } else { sternGerlachModel.isVisibleProperty.set( false ); } } ); + + // Set the probabilities of the experiment. In the continuous case, this immediately alters the shown rays + // In the single case, this prepares the probabilities for the particle that will be shot + // TODO: Given the above description, is measure() a correct name? https://github.com/phetsims/quantum-measurement/issues/53 + this.measure(); + } ); + + this.blochSphere.spinStateProperty.link( () => { + this.measure(); } ); this.currentlyShootingParticlesProperty = new Property( false, { @@ -120,6 +134,29 @@ export default class SpinModel implements TModel { } + public measure(): void { + const experimentSetting = this.currentExperimentProperty.value.experimentSetting; + + // Measure on the first SG, this will change its upProbabilityProperty + this.firstSternGerlachModel.measure( this.blochSphere.spinStateProperty.value ); + + if ( experimentSetting.length > 1 ) { + // Measure on the second SG according to the orientation of the first one + this.secondSternGerlachModel.measure( + // SG1 passes the up-spin particles to SG2 + this.firstSternGerlachModel.isZOrientedProperty.value ? SpinValue.Z_PLUS : SpinValue.X_PLUS + ); + + this.thirdSternGerlachModel.measure( + // SG1 passes the down-spin particles to SG3, and because X- is not in the initial spin values, we pass null + this.firstSternGerlachModel.isZOrientedProperty.value ? SpinValue.Z_MINUS : null + ); + + } + + this.probabilitiesUpdatedEmitter.emit(); + } + /** * Resets the model. */ diff --git a/js/spin/model/SternGerlachModel.ts b/js/spin/model/SternGerlachModel.ts index 817a019..1084be1 100644 --- a/js/spin/model/SternGerlachModel.ts +++ b/js/spin/model/SternGerlachModel.ts @@ -10,8 +10,13 @@ */ import BooleanProperty from '../../../../axon/js/BooleanProperty.js'; +import DerivedProperty from '../../../../axon/js/DerivedProperty.js'; +import NumberProperty from '../../../../axon/js/NumberProperty.js'; +import TReadOnlyProperty from '../../../../axon/js/TReadOnlyProperty.js'; +import Vector2 from '../../../../dot/js/Vector2.js'; import Tandem from '../../../../tandem/js/Tandem.js'; import quantumMeasurement from '../../quantumMeasurement.js'; +import { SpinValue } from './SpinModel.js'; export default class SternGerlachModel { @@ -19,6 +24,9 @@ export default class SternGerlachModel { public readonly isVisibleProperty: BooleanProperty; + public readonly upProbabilityProperty: NumberProperty; + public readonly downProbabilityProperty: TReadOnlyProperty; + public constructor( isZOriented: boolean, tandem: Tandem ) { this.isZOrientedProperty = new BooleanProperty( isZOriented, { @@ -29,8 +37,36 @@ export default class SternGerlachModel { tandem: tandem.createTandem( 'isVisibleProperty' ) } ); + this.upProbabilityProperty = new NumberProperty( 0.5, { + tandem: tandem.createTandem( 'upProbabilityProperty' ) + } ); + + this.downProbabilityProperty = new DerivedProperty( [ this.upProbabilityProperty ], upProbability => 1 - upProbability ); + + } + + + /** + * Measures incoming particles with a given state (Z+, Z-, X+), and returns the probability of spin up + * in the direction of the Stern Gerlach experiment. + */ + public measure( incomingState: SpinValue | null ): void { + + // Using a XZ vector to calculate the projected probability. + // The experiment has a measurement vector and the incoming state has a spin vector + // Based on the dot product we'll obtain the probability + + const incomingStateVector = incomingState === SpinValue.Z_PLUS ? new Vector2( 0, 1 ) : + incomingState === SpinValue.Z_MINUS ? new Vector2( 0, -1 ) : + incomingState === SpinValue.X_PLUS ? new Vector2( 1, 0 ) : new Vector2( -1, 0 ); + + const experimentMeasurementVector = this.isZOrientedProperty.value ? new Vector2( 0, 1 ) : new Vector2( 1, 0 ); + + // = 1, = 0, <-Z|Z> = -1 so we need to re-scale into the [0, 1] range + this.upProbabilityProperty.value = ( incomingStateVector.dot( experimentMeasurementVector ) + 1 ) / 2; } + public reset(): void { // no-op TODO https://github.com/phetsims/quantum-measurement/issues/53 } diff --git a/js/spin/view/ParticleRayPath.ts b/js/spin/view/ParticleRayPath.ts index fb67ad5..06c14ec 100644 --- a/js/spin/view/ParticleRayPath.ts +++ b/js/spin/view/ParticleRayPath.ts @@ -7,7 +7,6 @@ * @author Agustín Vallejo */ -import Multilink from '../../../../axon/js/Multilink.js'; import TReadOnlyProperty from '../../../../axon/js/TReadOnlyProperty.js'; import Vector2 from '../../../../dot/js/Vector2.js'; import { Shape } from '../../../../kite/js/imports.js'; @@ -36,8 +35,6 @@ export default class ParticleRayPath extends Node { lineCap: 'round' }; - let rayOpacity = 1; - // Create 7 paths for the possible rays const rayPaths = _.range( 7 ).map( i => { return new Path( null, rayPathOptions ); @@ -60,7 +57,7 @@ export default class ParticleRayPath extends Node { const mappedRayPoints = rayPointsPair.map( point => this.globalToLocalPoint( point ) ); rayPaths[ i ].shape = new Shape().moveTo( mappedRayPoints[ 0 ].x, mappedRayPoints[ 0 ].y ) .lineTo( mappedRayPoints[ 1 ].x, mappedRayPoints[ 1 ].y ); - rayPaths[ i ].opacity = rayOpacity; + rayPaths[ i ].opacity = 1; } else { rayPaths[ i ].shape = null; @@ -80,15 +77,6 @@ export default class ParticleRayPath extends Node { } } }; - - // TODO: Multilink for when we have another value in the model for the opacity of secondary and tertiary rays https://github.com/phetsims/quantum-measurement/issues/53 - Multilink.multilink( - [ particleAmmountProperty ], - particleAmmount => { - rayOpacity = particleAmmount; - this.updateOpacities( _.times( 7, () => rayOpacity ) ); - } - ); } } diff --git a/js/spin/view/SpinMeasurementArea.ts b/js/spin/view/SpinMeasurementArea.ts index 386bc47..effa8b4 100644 --- a/js/spin/view/SpinMeasurementArea.ts +++ b/js/spin/view/SpinMeasurementArea.ts @@ -22,6 +22,8 @@ import SternGerlachNode from './SternGerlachNode.js'; export default class SpinMeasurementArea extends VBox { + public readonly particleRayPath: ParticleRayPath; + public constructor( model: SpinModel, parentNode: Node, layoutBounds: Bounds2, tandem: Tandem ) { const items: ComboBoxItem[] = SpinExperiment.enumeration.values.map( experiment => { @@ -46,6 +48,8 @@ export default class SpinMeasurementArea extends VBox { tandem.createTandem( 'particleRayPath' ) ); + // Since this is a VBox, we add the ray path to the container node + // TODO: Currently parent is screenVies, Will this cause trouble if an intermediate parent is added? see https://github.com/phetsims/quantum-measurement/issues/53 parentNode.addChild( particleRayPath ); super( { @@ -74,6 +78,28 @@ export default class SpinMeasurementArea extends VBox { margin: 20 } ); + this.particleRayPath = particleRayPath; + + const updateRayOpacities = ( particleAmmount: number ) => { + particleRayPath.updateOpacities( [ + particleAmmount, // First ray only depends on the initial particle ammount + model.firstSternGerlachModel.upProbabilityProperty.value * particleAmmount, + model.firstSternGerlachModel.downProbabilityProperty.value * particleAmmount, + model.firstSternGerlachModel.upProbabilityProperty.value * model.secondSternGerlachModel.upProbabilityProperty.value * particleAmmount, + model.firstSternGerlachModel.upProbabilityProperty.value * model.secondSternGerlachModel.downProbabilityProperty.value * particleAmmount, + model.firstSternGerlachModel.downProbabilityProperty.value * model.thirdSternGerlachModel.upProbabilityProperty.value * particleAmmount, + model.firstSternGerlachModel.downProbabilityProperty.value * model.thirdSternGerlachModel.downProbabilityProperty.value * particleAmmount + ] ); + }; + + model.particleAmmountProperty.link( particleAmmount => { + updateRayOpacities( particleAmmount ); + } ); + + model.probabilitiesUpdatedEmitter.addListener( () => { + updateRayOpacities( model.particleAmmountProperty.value ); + } ); + model.currentExperimentProperty.link( experiment => { // Adjust the global positions of the nodes @@ -87,7 +113,7 @@ export default class SpinMeasurementArea extends VBox { const endOfRays = layoutBounds.maxX * 5; - if ( experiment.experimentSettings.length === 1 ) { + if ( experiment.experimentSetting.length === 1 ) { particleRayPath.updatePaths( [ primaryRayPoints, [ firstSternGerlachNode.topExitGlobalPosition, new Vector2( endOfRays, firstSternGerlachNode.topExitGlobalPosition.y ) ], @@ -105,6 +131,8 @@ export default class SpinMeasurementArea extends VBox { [ thirdSternGerlachNode.bottomExitGlobalPosition, new Vector2( endOfRays, thirdSternGerlachNode.bottomExitGlobalPosition.y ) ] ] ); } + + updateRayOpacities( model.particleAmmountProperty.value ); } ); } } diff --git a/js/spin/view/SpinScreenView.ts b/js/spin/view/SpinScreenView.ts index aee3e95..15a4037 100644 --- a/js/spin/view/SpinScreenView.ts +++ b/js/spin/view/SpinScreenView.ts @@ -48,6 +48,7 @@ export default class SpinScreenView extends QuantumMeasurementScreenView { this.mockupOpacityProperty && this.mockupOpacityProperty.link( opacity => { spinStatePreparationArea.opacity = 1 - opacity; spinMeasurementArea.opacity = 1 - opacity; + spinMeasurementArea.particleRayPath.opacity = 1 - opacity; } ); model.currentExperimentProperty.notifyListenersStatic(); diff --git a/js/spin/view/SternGerlachNode.ts b/js/spin/view/SternGerlachNode.ts index dce92cc..7461cec 100644 --- a/js/spin/view/SternGerlachNode.ts +++ b/js/spin/view/SternGerlachNode.ts @@ -76,7 +76,7 @@ export default class SternGerlachNode extends Node { new RichText( new DerivedProperty( [ experimentModel.isZOrientedProperty ], ( isZOriented: boolean ) => isZOriented ? 'SGZ' : 'SGX' ), - { font: new PhetFont( 16 ), fill: 'white', center: new Vector2( 25, 80 ) } ) + { font: new PhetFont( 20 ), fill: 'white', center: new Vector2( 25, 80 ) } ) ] } ); }