From b152d13e51e971e152a6b370cb47402f6806bdcf Mon Sep 17 00:00:00 2001 From: AgustinVallejo Date: Tue, 17 Sep 2024 15:17:43 -0500 Subject: [PATCH] Adding support for a third 'superposed' uncollapsed quantum state to better control the basis, see https://github.com/phetsims/quantum-measurement/issues/28 --- js/coins/model/CoinsExperimentSceneModel.ts | 26 ++++++-- js/coins/model/QuantumCoinStates.ts | 7 +- js/coins/view/InitialCoinStateSelectorNode.ts | 64 +++++++++---------- 3 files changed, 56 insertions(+), 41 deletions(-) diff --git a/js/coins/model/CoinsExperimentSceneModel.ts b/js/coins/model/CoinsExperimentSceneModel.ts index a88d7be..6f2cc3f 100644 --- a/js/coins/model/CoinsExperimentSceneModel.ts +++ b/js/coins/model/CoinsExperimentSceneModel.ts @@ -20,7 +20,7 @@ import TwoStateSystem from '../../common/model/TwoStateSystem.js'; import TwoStateSystemSet from '../../common/model/TwoStateSystemSet.js'; import quantumMeasurement from '../../quantumMeasurement.js'; import { ClassicalCoinStates, ClassicalCoinStateValues } from './ClassicalCoinStates.js'; -import { QuantumCoinStates, QuantumCoinStateValues } from './QuantumCoinStates.js'; +import { QuantumCoinStates, QuantumCoinStateValues, QuantumUncollapsedCoinStates, QuantumUncollapsedCoinStateValues } from './QuantumCoinStates.js'; type SelfOptions = { initiallyActive?: boolean; @@ -46,7 +46,7 @@ export default class CoinsExperimentSceneModel extends PhetioObject { public readonly coinSet: TwoStateSystemSet; // The initial state of the coin(s) before any flipping or other experiment preparation occurs. - public readonly initialCoinStateProperty: Property | Property; + public readonly initialCoinStateProperty: Property | Property; // The bias towards one outcome or another in the initially prepared state, from 0 to 1. public readonly stateBiasProperty: NumberProperty; @@ -95,10 +95,10 @@ export default class CoinsExperimentSceneModel extends PhetioObject { } else { assert && assert( options.systemType === 'quantum', 'unhandled system type' ); - this.initialCoinStateProperty = new Property( 'up', { + this.initialCoinStateProperty = new Property( 'up', { tandem: options.tandem.createTandem( 'initialCoinStateProperty' ), - phetioValueType: StringUnionIO( QuantumCoinStateValues ), - validValues: QuantumCoinStateValues + phetioValueType: StringUnionIO( QuantumUncollapsedCoinStateValues ), + validValues: QuantumUncollapsedCoinStateValues } ); this.singleCoin = new TwoStateSystem( QuantumCoinStateValues, @@ -128,14 +128,26 @@ export default class CoinsExperimentSceneModel extends PhetioObject { // chosen by the user so that it will match when it animates into the test box and be correct if revealed right // away. this.singleCoin.setMeasurementValueImmediate( this.initialCoinStateProperty.value as never ); - this.coinSet.setMeasurementValuesImmediate( this.initialCoinStateProperty.value ); + this.coinSet.setMeasurementValuesImmediate( this.initialCoinStateProperty.value as QuantumCoinStates | ClassicalCoinStates ); } } ); // If this is a quantum system, changing the initial state of the coin sets the bias to match that coin. if ( this.systemType === 'quantum' ) { this.initialCoinStateProperty.lazyLink( initialCoinState => { - this.stateBiasProperty.value = initialCoinState === 'up' ? 1 : 0; + if ( initialCoinState !== 'superposed' ) { + this.stateBiasProperty.value = initialCoinState === 'up' ? 1 : 0; + } + } ); + + this.stateBiasProperty.lazyLink( bias => { + if ( bias !== 0 && bias !== 1 ) { + this.initialCoinStateProperty.value = 'superposed'; + } + else { + // TODO: Wouldn't this cause a reentry? https://github.com/phetsims/quantum-measurement/issues/28 + this.initialCoinStateProperty.value = bias === 1 ? 'up' : 'down'; + } } ); } } diff --git a/js/coins/model/QuantumCoinStates.ts b/js/coins/model/QuantumCoinStates.ts index d56b537..a15c92a 100644 --- a/js/coins/model/QuantumCoinStates.ts +++ b/js/coins/model/QuantumCoinStates.ts @@ -6,5 +6,10 @@ * @author John Blanco (PhET Interactive Simulations) */ +// Collapsed states after measurements export const QuantumCoinStateValues = [ 'up', 'down' ] as const; -export type QuantumCoinStates = ( typeof QuantumCoinStateValues )[number]; \ No newline at end of file +export type QuantumCoinStates = ( typeof QuantumCoinStateValues )[number]; + +// Uncollapsed states when preparing the coin +export const QuantumUncollapsedCoinStateValues = [ 'up', 'down', 'superposed' ] as const; +export type QuantumUncollapsedCoinStates = ( typeof QuantumUncollapsedCoinStateValues )[number]; \ No newline at end of file diff --git a/js/coins/view/InitialCoinStateSelectorNode.ts b/js/coins/view/InitialCoinStateSelectorNode.ts index 43a9904..2c88202 100644 --- a/js/coins/view/InitialCoinStateSelectorNode.ts +++ b/js/coins/view/InitialCoinStateSelectorNode.ts @@ -11,16 +11,17 @@ import NumberProperty from '../../../../axon/js/NumberProperty.js'; import Property from '../../../../axon/js/Property.js'; import TReadOnlyProperty from '../../../../axon/js/TReadOnlyProperty.js'; +import { combineOptions } from '../../../../phet-core/js/optionize.js'; import PhetFont from '../../../../scenery-phet/js/PhetFont.js'; -import { Color, Text, VBox } from '../../../../scenery/js/imports.js'; -import RectangularRadioButtonGroup, { RectangularRadioButtonGroupOptions } from '../../../../sun/js/buttons/RectangularRadioButtonGroup.js'; +import { Color, HBox, Text, VBox } from '../../../../scenery/js/imports.js'; +import RectangularRadioButton, { RectangularRadioButtonOptions } from '../../../../sun/js/buttons/RectangularRadioButton.js'; import Panel from '../../../../sun/js/Panel.js'; import Tandem from '../../../../tandem/js/Tandem.js'; import { SystemType } from '../../common/model/SystemType.js'; import quantumMeasurement from '../../quantumMeasurement.js'; import QuantumMeasurementStrings from '../../QuantumMeasurementStrings.js'; import { ClassicalCoinStates, ClassicalCoinStateValues } from '../model/ClassicalCoinStates.js'; -import { QuantumCoinStates, QuantumCoinStateValues } from '../model/QuantumCoinStates.js'; +import { QuantumCoinStates, QuantumCoinStateValues, QuantumUncollapsedCoinStates } from '../model/QuantumCoinStates.js'; import ClassicalCoinNode from './ClassicalCoinNode.js'; import CoinNode from './CoinNode.js'; import QuantumCoinNode from './QuantumCoinNode.js'; @@ -32,7 +33,7 @@ export default class InitialCoinStateSelectorNode extends VBox { public readonly orientationIndicatorCoinNode: CoinNode; - public constructor( initialCoinStateProperty: TReadOnlyProperty | TReadOnlyProperty, + public constructor( initialCoinStateProperty: TReadOnlyProperty | TReadOnlyProperty, stateBiasProperty: TReadOnlyProperty, preparingExperimentProperty: TReadOnlyProperty, systemType: SystemType, @@ -54,52 +55,49 @@ export default class InitialCoinStateSelectorNode extends VBox { font: new PhetFont( { size: 18, weight: 'bold' } ) } ); - const radioButtonGroupOptions: RectangularRadioButtonGroupOptions = { - orientation: 'horizontal', - spacing: 22, - radioButtonOptions: { + const radioButtonOptions = { xMargin: 4, yMargin: 4, baseColor: Color.WHITE - }, - tandem: tandem.createTandem( 'initialOrientationRadioButtonGroup' ) + }; + + const createCoinRadioButton = ( stateValue: string, coinNode: CoinNode ) => { + return new RectangularRadioButton( + initialCoinStateProperty as Property, + stateValue, + combineOptions( { + content: coinNode, + tandem: tandem.createTandem( `${stateValue.toLowerCase()}RadioButton` ) + }, radioButtonOptions ) + ); }; - let initialOrientationRadioButtonGroup; + let initialCoinStateItems; if ( systemType === 'classical' ) { - const initialCoinStateItems = ClassicalCoinStateValues.map( stateValue => ( { - createNode: () => new ClassicalCoinNode( + initialCoinStateItems = ClassicalCoinStateValues.map( stateValue => { + return createCoinRadioButton( stateValue, new ClassicalCoinNode( new Property( stateValue ), RADIO_BUTTON_COIN_NODE_RADIUS, Tandem.OPT_OUT - ), - value: stateValue, - tandemName: `${stateValue.toLowerCase()}RadioButton` - } ) ); - initialOrientationRadioButtonGroup = new RectangularRadioButtonGroup( - initialCoinStateProperty as Property, - initialCoinStateItems, - radioButtonGroupOptions - ); + ) ); + } ); } else { - const initialCoinStateItems = QuantumCoinStateValues.map( stateValue => ( { - createNode: () => new QuantumCoinNode( + initialCoinStateItems = QuantumCoinStateValues.map( stateValue => { + return createCoinRadioButton( stateValue, new QuantumCoinNode( new Property( stateValue ), new NumberProperty( stateValue === 'up' ? 1 : 0 ), RADIO_BUTTON_COIN_NODE_RADIUS, Tandem.OPT_OUT - ), - value: stateValue, - tandemName: `${stateValue.toLowerCase()}RadioButton` - } ) ); - initialOrientationRadioButtonGroup = new RectangularRadioButtonGroup( - initialCoinStateProperty as Property, - initialCoinStateItems, - radioButtonGroupOptions - ); + ) ); + } ); } + const initialOrientationRadioButtonGroup = new HBox( { + children: initialCoinStateItems, + spacing: 22 + } ); + const selectorPanelContent = new VBox( { children: [ selectionPanelTitle, initialOrientationRadioButtonGroup ], spacing: 10