diff --git a/js/coins/model/CoinsExperimentSceneModel.ts b/js/coins/model/CoinsExperimentSceneModel.ts index 2ce11ec..10593e6 100644 --- a/js/coins/model/CoinsExperimentSceneModel.ts +++ b/js/coins/model/CoinsExperimentSceneModel.ts @@ -129,8 +129,8 @@ export default class CoinsExperimentSceneModel extends PhetioObject { if ( preparingExperiment ) { // Set the coin measurement states back to their initial values. - this.singleCoin.prepareInstantly(); - this.coinSet.prepareInstantly(); + this.singleCoin.prepareNow(); + this.coinSet.prepareNow(); } else { diff --git a/js/coins/model/ExperimentMeasurementState.ts b/js/coins/model/ExperimentMeasurementState.ts index 7c81967..b1b74ad 100644 --- a/js/coins/model/ExperimentMeasurementState.ts +++ b/js/coins/model/ExperimentMeasurementState.ts @@ -2,25 +2,27 @@ /** * ExperimentMeasurementState is a string union that functions as an enumeration of the possible states for a basic - * measurement experiment. It essentially encodes two pieces of information, one being whether the state of the - * experiment has been measured and the other being whether the state is hidden or shown. Because the combination - * of not measured and shown is not possible, this amounts to three states. See the comments below for each state for - * more details. + * measurement experiment. The possible values of the states are intended to support the measurement of both classical + * and quantum systems. The states used and the transitions between them will be somewhat different for classical + * versus quantum systems. * * @author John Blanco (PhET Interactive Simulations) */ export const ExperimentMeasurementStateValues = [ - // The experiment is prepared and can be measured, but hasn't been yet. - 'readyToBeMeasured', - // The experiment is in the process of preparing to be measured. For example, in the case of a coin flipping // experiment, this is the state when the coin is being flipped. 'preparingToBeMeasured', - // The experiment's state has been measured and revealed to the observer(s). - 'measuredAndRevealed' + // The experiment is prepared and can be measured, but hasn't been yet. This state only applies to quantum systems. + 'readyToBeMeasured', + + // The experiment is in a state where the values are determined but are not currently being shown to the user. + 'measuredAndHidden', + + // The experiment's state has been revealed to the observer(s). + 'revealed' ] as const; diff --git a/js/coins/view/CoinExperimentButtonSet.ts b/js/coins/view/CoinExperimentButtonSet.ts index fb06898..0c93687 100644 --- a/js/coins/view/CoinExperimentButtonSet.ts +++ b/js/coins/view/CoinExperimentButtonSet.ts @@ -16,8 +16,6 @@ import PickRequired from '../../../../phet-core/js/types/PickRequired.js'; import PhetFont from '../../../../scenery-phet/js/PhetFont.js'; import { Color, NodeOptions, VBox, VBoxOptions } from '../../../../scenery/js/imports.js'; import TextPushButton, { TextPushButtonOptions } from '../../../../sun/js/buttons/TextPushButton.js'; -import { SystemType } from '../../common/model/SystemType.js'; -import TwoStateSystem from '../../common/model/TwoStateSystem.js'; import TwoStateSystemSet from '../../common/model/TwoStateSystemSet.js'; import quantumMeasurement from '../../quantumMeasurement.js'; import QuantumMeasurementStrings from '../../QuantumMeasurementStrings.js'; @@ -31,8 +29,7 @@ const BUTTON_WIDTH = 160; // empirically determined to match spec export default class CoinExperimentButtonSet extends VBox { - public constructor( system: TwoStateSystem | TwoStateSystemSet, - systemType: SystemType, + public constructor( system: TwoStateSystemSet, testBoxReadyProperty: TProperty, providedOptions: CoinExperimentButtonSetOptions ) { @@ -62,10 +59,10 @@ export default class CoinExperimentButtonSet extends VBox { ], ( hideString, revealString, observeString, experimentState ) => { let labelString; - if ( experimentState === 'measuredAndRevealed' ) { + if ( experimentState === 'revealed' ) { labelString = hideString; } - else if ( systemType === 'classical' ) { + else if ( system.systemType === 'classical' ) { labelString = revealString; } else { @@ -80,10 +77,12 @@ export default class CoinExperimentButtonSet extends VBox { revealHideButtonTextProperty, combineOptions( commonButtonOptions, { listener: () => { - if ( system.measurementStateProperty.value === 'readyToBeMeasured' ) { - system.measure(); + if ( system.measurementStateProperty.value === 'readyToBeMeasured' || + system.measurementStateProperty.value === 'measuredAndHidden' ) { + + system.reveal(); } - else if ( system.measurementStateProperty.value === 'measuredAndRevealed' ) { + else if ( system.measurementStateProperty.value === 'revealed' ) { system.hide(); } }, @@ -92,7 +91,7 @@ export default class CoinExperimentButtonSet extends VBox { ); const flipOrReprepareButton = new TextPushButton( - systemType === 'classical' ? + system.systemType === 'classical' ? QuantumMeasurementStrings.flipStringProperty : QuantumMeasurementStrings.reprepareStringProperty, combineOptions( commonButtonOptions, { @@ -102,7 +101,7 @@ export default class CoinExperimentButtonSet extends VBox { ); const flipOrReprepareAndRevealButton = new TextPushButton( - systemType === 'classical' ? + system.systemType === 'classical' ? QuantumMeasurementStrings.flipAndRevealStringProperty : QuantumMeasurementStrings.reprepareAndRevealStringProperty, combineOptions( commonButtonOptions, { diff --git a/js/coins/view/CoinExperimentMeasurementArea.ts b/js/coins/view/CoinExperimentMeasurementArea.ts index 3528111..824c2a0 100644 --- a/js/coins/view/CoinExperimentMeasurementArea.ts +++ b/js/coins/view/CoinExperimentMeasurementArea.ts @@ -28,7 +28,6 @@ import MultiCoinTestBox from './MultiCoinTestBox.js'; import MultipleCoinAnimations from './MultipleCoinAnimations.js'; import SceneSectionHeader from './SceneSectionHeader.js'; import SingleCoinAnimations from './SingleCoinAnimations.js'; -import TwoStateSystem from '../../common/model/TwoStateSystem.js'; import SingleCoinTestBox from './SingleCoinTestBox.js'; const RADIO_BUTTON_FONT = new PhetFont( 12 ); @@ -68,8 +67,7 @@ export default class CoinExperimentMeasurementArea extends VBox { // Create the buttons that will be used to control the single-coin test box. const singleCoinExperimentButtonSet = new CoinExperimentButtonSet( - sceneModel.singleCoin as TwoStateSystem, - sceneModel.systemType, + sceneModel.singleCoin, singleCoinInTestBoxProperty, { tandem: tandem.createTandem( 'singleCoinExperimentButtonSet' ), @@ -96,6 +94,7 @@ export default class CoinExperimentMeasurementArea extends VBox { sceneModel.coinSet, sceneModel.coinSet.measurementStateProperty, sceneModel.coinSet.numberOfActiveSystemsProperty, + sceneModel.coinSet.measuredDataChanged, { tandem: tandem.createTandem( 'multipleCoinTestBox' ) } ); const multiCoinExperimentHistogram = new CoinMeasurementHistogram( sceneModel.coinSet, sceneModel.systemType, { @@ -104,7 +103,6 @@ export default class CoinExperimentMeasurementArea extends VBox { } ); const multipleCoinExperimentButtonSet = new CoinExperimentButtonSet( sceneModel.coinSet, - sceneModel.systemType, coinSetInTestBoxProperty, { tandem: tandem.createTandem( 'multipleCoinExperimentButtonSet' ), @@ -202,7 +200,7 @@ export default class CoinExperimentMeasurementArea extends VBox { this.measuredCoinsPixelRepresentation.setY( offset ); sceneModel.coinSet.measurementStateProperty.link( measurementState => { - if ( measurementState === 'measuredAndRevealed' ) { + if ( measurementState === 'revealed' ) { this.measuredCoinsPixelRepresentation.redraw( sceneModel.coinSet.measuredValues ); } } ); @@ -218,7 +216,7 @@ export default class CoinExperimentMeasurementArea extends VBox { visibleProperty: new DerivedProperty( [ sceneModel.preparingExperimentProperty, sceneModel.singleCoin.measurementStateProperty ], ( preparingExperiment, singleCoinExperimentState ) => - !preparingExperiment && singleCoinExperimentState !== 'measuredAndRevealed' + !preparingExperiment && singleCoinExperimentState !== 'revealed' ) } ); singleCoinTestBox.clippedTestBox.addChild( coinMask ); diff --git a/js/coins/view/CoinMeasurementHistogram.ts b/js/coins/view/CoinMeasurementHistogram.ts index 7a46435..ab4731f 100644 --- a/js/coins/view/CoinMeasurementHistogram.ts +++ b/js/coins/view/CoinMeasurementHistogram.ts @@ -56,7 +56,7 @@ export default class CoinMeasurementHistogram extends Node { // Create a Property that controls whether the values should be displayed. const displayValuesProperty = DerivedProperty.valueEqualsConstant( coinSet.measurementStateProperty, - 'measuredAndRevealed' + 'revealed' ); // Create the X and Y axes. @@ -116,7 +116,7 @@ export default class CoinMeasurementHistogram extends Node { const testValue = systemType === 'classical' ? 'heads' : 'up'; let total = 0; - if ( measurementState === 'measuredAndRevealed' ) { + if ( measurementState === 'revealed' ) { _.times( numberOfActiveSystems, i => { if ( coinSet.measuredValues[ i ] === testValue ) { total++; @@ -134,7 +134,7 @@ export default class CoinMeasurementHistogram extends Node { const testValue = systemType === 'classical' ? 'tails' : 'down'; let total = 0; - if ( measurementState === 'measuredAndRevealed' ) { + if ( measurementState === 'revealed' ) { _.times( numberOfActiveSystems, i => { if ( coinSet.measuredValues[ i ] === testValue ) { total++; diff --git a/js/coins/view/CoinSetPixelRepresentation.ts b/js/coins/view/CoinSetPixelRepresentation.ts index f97add9..8654efa 100644 --- a/js/coins/view/CoinSetPixelRepresentation.ts +++ b/js/coins/view/CoinSetPixelRepresentation.ts @@ -137,7 +137,7 @@ export default class CoinSetPixelRepresentation extends CanvasNode { 'transparent'; }; break; - case 'measuredAndRevealed': + case 'revealed': getColor = ( value: number ) => { return value === 1 ? 'black' : 'fuchsia'; }; diff --git a/js/coins/view/MultiCoinTestBox.ts b/js/coins/view/MultiCoinTestBox.ts index 1f4af77..7d6ffd6 100644 --- a/js/coins/view/MultiCoinTestBox.ts +++ b/js/coins/view/MultiCoinTestBox.ts @@ -23,6 +23,7 @@ import quantumMeasurement from '../../quantumMeasurement.js'; import { MAX_COINS, MULTI_COIN_EXPERIMENT_QUANTITIES } from '../model/CoinsExperimentSceneModel.js'; import { ExperimentMeasurementState } from '../model/ExperimentMeasurementState.js'; import SmallCoinNode from './SmallCoinNode.js'; +import TEmitter from '../../../../axon/js/TEmitter.js'; type SelfOptions = EmptySelfOptions; export type MultiCoinTestBoxOptions = SelfOptions & WithRequired; @@ -45,6 +46,7 @@ export default class MultiCoinTestBox extends HBox { public constructor( coinSet: TwoStateSystemSet, measurementStateProperty: Property, numberOfActiveSystemsProperty: TReadOnlyProperty, + measuredDataChangedEmitter: TEmitter, providedOptions?: MultiCoinTestBoxOptions ) { // Create the main rectangle that will represent the area where the coins will be hidden and shown. @@ -88,7 +90,7 @@ export default class MultiCoinTestBox extends HBox { measurementStateProperty.link( measurementState => { // Make the box look hazy when the measurement is not revealed. - multipleCoinTestBoxRectangle.fill = measurementState === 'measuredAndRevealed' ? + multipleCoinTestBoxRectangle.fill = measurementState === 'revealed' ? TEST_BOX_CONTENTS_REVEALED_FILL : TEST_BOX_CONTENTS_HIDDEN_FILL; @@ -96,10 +98,12 @@ export default class MultiCoinTestBox extends HBox { this.updateCoinNodes( coinSet, measurementState ); } ); - // When phet-io state is being set, it is possible for the coin states to change without there being any change in - // the state of the experiment. The following code makes sure that coin nodes are updated if necessary. - isSettingPhetioStateProperty.lazyLink( isSettingPhetioState => { - if ( !isSettingPhetioState && measurementStateProperty.value === 'measuredAndRevealed' ) { + // Update the appearance of the coin nodes when the data changes. + measuredDataChangedEmitter.addListener( () => { + + // When phet-io state is being set, the measured data can change without any change to the measurement state of + // the coin set. This makes sure that the coin nodes are updated in that situation. + if ( isSettingPhetioStateProperty.value ) { this.updateCoinNodes( coinSet, measurementStateProperty.value ); } } ); @@ -113,7 +117,7 @@ export default class MultiCoinTestBox extends HBox { private updateCoinNodes( coinSet: TwoStateSystemSet, measurementState: ExperimentMeasurementState ): void { this.residentCoinNodes.forEach( ( coinNode, index ) => { - if ( measurementState === 'measuredAndRevealed' ) { + if ( measurementState === 'revealed' ) { if ( coinNode.isFlipping ) { coinNode.stopFlipping(); diff --git a/js/coins/view/MultipleCoinAnimations.ts b/js/coins/view/MultipleCoinAnimations.ts index 691432b..39b7cba 100644 --- a/js/coins/view/MultipleCoinAnimations.ts +++ b/js/coins/view/MultipleCoinAnimations.ts @@ -152,7 +152,7 @@ public readonly startIngressAnimationForCoinSet: ( forReprepare: boolean ) => vo multipleCoinTestBox.addCoinNodeToBox( coinNode ); if ( sceneModel.systemType === 'quantum' ) { - sceneModel.coinSet.prepareInstantly(); + sceneModel.coinSet.prepareNow(); } // If all animations have completed, set the flag that indicates the coins are fully in the box. diff --git a/js/coins/view/SingleCoinAnimations.ts b/js/coins/view/SingleCoinAnimations.ts index fc33062..f9714a0 100644 --- a/js/coins/view/SingleCoinAnimations.ts +++ b/js/coins/view/SingleCoinAnimations.ts @@ -187,7 +187,7 @@ export default class SingleCoinAnimations { // "Collapse" the state of the coin node so that it shows a single state, not a superposed one. const quantumCoinNode = singleCoinNode as QuantumCoinNode; quantumCoinNode.showSuperpositionProperty.value = false; - sceneModel.singleCoin.prepareInstantly(); + sceneModel.singleCoin.prepareNow(); } // The coin is in the test box, so update the flag that makes this known. diff --git a/js/coins/view/SingleCoinTestBox.ts b/js/coins/view/SingleCoinTestBox.ts index 983d3b1..011666a 100644 --- a/js/coins/view/SingleCoinTestBox.ts +++ b/js/coins/view/SingleCoinTestBox.ts @@ -53,7 +53,7 @@ export default class SingleCoinTestBox extends Node { // Make the box transparent when the state of the measurement indicates that the coin is revealed to the user. measurementStateProperty.link( singleCoinMeasurementState => { - testBoxRectangle.fill = singleCoinMeasurementState === 'measuredAndRevealed' ? + testBoxRectangle.fill = singleCoinMeasurementState === 'revealed' ? Color.TRANSPARENT : SINGLE_COIN_TEST_BOX_UNREVEALED_FILL; } ); diff --git a/js/common/model/TwoStateSystem.ts b/js/common/model/TwoStateSystem.ts index 9aaf0db..3f45371 100644 --- a/js/common/model/TwoStateSystem.ts +++ b/js/common/model/TwoStateSystem.ts @@ -10,7 +10,7 @@ import NumberProperty from '../../../../axon/js/NumberProperty.js'; import optionize, { EmptySelfOptions } from '../../../../phet-core/js/optionize.js'; import quantumMeasurement from '../../quantumMeasurement.js'; -import TwoStateSystemSet, { StateSetMeasurementResult, TwoStateSystemSetOptions } from './TwoStateSystemSet.js'; +import TwoStateSystemSet, { TwoStateSystemSetOptions } from './TwoStateSystemSet.js'; import StrictOmit from '../../../../phet-core/js/types/StrictOmit.js'; import Property from '../../../../axon/js/Property.js'; import StringUnionIO from '../../../../tandem/js/types/StringUnionIO.js'; @@ -25,7 +25,7 @@ export default class TwoStateSystem extends TwoStateSystemSet< public readonly measuredValueProperty: Property; public constructor( stateValues: readonly T[], - initialState: T | null, + initialState: T, biasProperty: NumberProperty, providedOptions: TwoStateSystemOptions ) { @@ -40,17 +40,11 @@ export default class TwoStateSystem extends TwoStateSystemSet< phetioValueType: NullableIO( StringUnionIO( stateValues ) ), validValues: [ ...stateValues, null ] } ); - } - /** - * Override the measure function - */ - public override measure(): StateSetMeasurementResult { - const measurementResults = super.measure(); - assert && assert( measurementResults.length === 1, 'unexpected length for measurement set' ); - assert && assert( measurementResults.measuredValues[ 0 ] !== null, 'measurement result should not be indeterminate' ); - this.measuredValueProperty.set( measurementResults.measuredValues[ 0 ] ); - return measurementResults; + // Hook up to the data-changed emitter to update the data Property. + this.measuredDataChanged.addListener( () => { + this.measuredValueProperty.value = this.measuredValues[ 0 ]; + } ); } /** diff --git a/js/common/model/TwoStateSystemSet.ts b/js/common/model/TwoStateSystemSet.ts index 20fdf27..15ca4dc 100644 --- a/js/common/model/TwoStateSystemSet.ts +++ b/js/common/model/TwoStateSystemSet.ts @@ -19,11 +19,11 @@ import StringUnionIO from '../../../../tandem/js/types/StringUnionIO.js'; import { ExperimentMeasurementState, ExperimentMeasurementStateValues } from '../../coins/model/ExperimentMeasurementState.js'; import quantumMeasurement from '../../quantumMeasurement.js'; import { MULTI_COIN_EXPERIMENT_QUANTITIES } from '../../coins/model/CoinsExperimentSceneModel.js'; -import IOType from '../../../../tandem/js/types/IOType.js'; -import ArrayIO from '../../../../tandem/js/types/ArrayIO.js'; -import StringIO from '../../../../tandem/js/types/StringIO.js'; -import NullableIO from '../../../../tandem/js/types/NullableIO.js'; import { SystemType } from './SystemType.js'; +import Random from '../../../../dot/js/Random.js'; +import TEmitter from '../../../../axon/js/TEmitter.js'; +import Emitter from '../../../../axon/js/Emitter.js'; +import isSettingPhetioStateProperty from '../../../../tandem/js/isSettingPhetioStateProperty.js'; type SelfOptions = { systemType?: SystemType; @@ -51,8 +51,8 @@ export default class TwoStateSystemSet extends PhetioObject { // valid values for a measurement public readonly validValues: readonly T[]; - // the values of most recent measurement, null indicates superposed (which only applies to quantum systems) - public readonly measuredValues: Array; + // The values of most recent measurement. These are only valid in some - and not all - measurement states. + public readonly measuredValues: Array; // The number of systems that will be measured each time a measurement is made. public readonly numberOfActiveSystemsProperty: NumberProperty; @@ -65,8 +65,18 @@ export default class TwoStateSystemSet extends PhetioObject { // second, and 0.5 means no bias. public readonly biasProperty: NumberProperty; + // The seed that was used to generate the most recent set of measured values. This exists solely to support phet-io - + // it is conveyed in the state information and used to regenerate the data when phet-io state is set. This is done + // to avoid sending values for every individual measurement, which could be 10000 values. + public readonly seedProperty: NumberProperty; + + // An emitter that fires when the measured data changed, which is essentially any time a new measurement is made after + // the system has been prepared for measurement. This is intended to be used as a signal to the view that and update + // of the information being presented to the user is needed. + public readonly measuredDataChanged: TEmitter = new Emitter(); + public constructor( stateValues: readonly T[], - initialState: T | null, + initialState: T, biasProperty: NumberProperty, providedOptions: TwoStateSystemSetOptions ) { @@ -76,7 +86,7 @@ export default class TwoStateSystemSet extends PhetioObject { systemType: 'quantum', initialBias: 0.5, maxNumberOfSystems: 10000, - phetioType: TwoStateSystemSet.TwoStateSystemSetIO + phetioState: false }, providedOptions ); super( options ); @@ -93,16 +103,48 @@ export default class TwoStateSystemSet extends PhetioObject { tandem: options.tandem.createTandem( 'numberOfActiveSystemsProperty' ) } ); - this.measurementStateProperty = new Property( 'readyToBeMeasured', { + // The initial system state differs for classical versus quantum systems. + const initialMeasurementState = this.systemType === 'classical' ? 'measuredAndHidden' : 'readyToBeMeasured'; + this.measurementStateProperty = new Property( initialMeasurementState, { tandem: options.tandem.createTandem( 'measurementStateProperty' ), phetioValueType: StringUnionIO( ExperimentMeasurementStateValues ), phetioReadOnly: true } ); - this.measuredValues = new Array( options.maxNumberOfSystems ); + + this.measuredValues = new Array( options.maxNumberOfSystems ); _.times( options.maxNumberOfSystems, i => { this.measuredValues[ i ] = initialState; } ); + this.biasProperty = biasProperty; + + // Create the seed Property. + this.seedProperty = new NumberProperty( 1, { + range: new Range( 0, 1 ), + tandem: options.tandem.createTandem( 'seedProperty' ) + } ); + + // Monitor the seed for the random number generator. If this changes while setting phet-io state, the data will + // need to be updated. + this.seedProperty.lazyLink( seed => { + + if ( isSettingPhetioStateProperty.value && this.measurementStateProperty.value === 'revealed' ) { + + this.generateNewMeasurementValues(); + + // Create the measured values. + const random = new Random( { seed: seed } ); + _.times( this.numberOfActiveSystemsProperty.value, i => { + + // Only make a new measurement if one doesn't exist for this element. Otherwise, just keep the existing value. + const valueSetIndex = random.nextDouble() < this.biasProperty.value ? 0 : 1; + this.measuredValues[ i ] = this.validValues[ valueSetIndex ]; + } ); + + // Signal that the data has been updated. + this.measuredDataChanged.emit(); + } + } ); } /** @@ -110,37 +152,75 @@ export default class TwoStateSystemSet extends PhetioObject { * a quantum system into a superposed state. After a timeout, this system will transition to a state where it is * ready to be measured. */ - public prepare( measureWhenPrepared = false ): void { + public prepare( revealWhenPrepared = false ): void { // Set the state to preparingToBeMeasured and start a timeout for the state to end. this.measurementStateProperty.value = 'preparingToBeMeasured'; - // Set the measured values to an indeterminate state until they are measured. - _.times( this.measuredValues.length, i => { - this.measuredValues[ i ] = null; - } ); - - // Create a timeout that will move the measurement state to 'readyToBeMeasured' after a fixed amount of time. + // Set a timeout for the preparation interval and automatically move to the next state when it fires. this.preparingToBeMeasuredTimeoutListener = stepTimer.setTimeout( () => { this.preparingToBeMeasuredTimeoutListener = null; - this.measurementStateProperty.value = 'readyToBeMeasured'; - if ( measureWhenPrepared ) { - this.measure(); + this.prepareNow(); + + if ( revealWhenPrepared ) { + this.reveal(); } }, MEASUREMENT_PREPARATION_TIME * 1000 ); } /** - * Prepare the system for measurement without transitioning through the 'preparingToBeMeasured' state. This is more - * the exception than the rule, but is needed in a case or two. + * Prepare the system for measurement now, i.e. without transitioning through the 'preparingToBeMeasured' state. */ - public prepareInstantly(): void { + public prepareNow(): void { - // Set the measured values to an indeterminate state until they are measured. - _.times( this.measuredValues.length, i => { - this.measuredValues[ i ] = null; - } ); - this.measurementStateProperty.value = 'readyToBeMeasured'; + if ( this.systemType === 'classical' ) { + + // Classical systems have deterministic values when measured. + this.generateNewMeasurementValues(); + this.measurementStateProperty.value = 'measuredAndHidden'; + } + else { + + // Quantum systems don't create values until revealed, so mark it as 'readyToBeMeasured'; + this.measurementStateProperty.value = 'readyToBeMeasured'; + } + } + + /** + * Set the system into a state that indicates that its values are revealed to the world. For a quantum system, this + * may cause new measured values to be set. + */ + public reveal(): void { + + // state checking + assert && assert( + this.measurementStateProperty.value === 'measuredAndHidden' || + this.measurementStateProperty.value === 'readyToBeMeasured', + 'This system is not in an appropriate state to be revealed' + ); + + if ( this.measurementStateProperty.value === 'readyToBeMeasured' ) { + assert && assert( this.systemType === 'quantum', 'This point should only be reached for quantum systems' ); + this.generateNewMeasurementValues(); + } + + // Update the measurement state to indicate revealed. When this happens, the view should present the values to the + // observer(s). + this.measurementStateProperty.value = 'revealed'; + } + + /** + * Set the system into a state where its values are hidden from the world. + */ + public hide(): void { + + // state checking + assert && assert( + this.measurementStateProperty.value === 'revealed', + 'This system is not in an appropriate state to be hidden' + ); + + this.measurementStateProperty.value = 'measuredAndHidden'; } /** @@ -148,27 +228,43 @@ export default class TwoStateSystemSet extends PhetioObject { * will just return the value of the most recent measurement. */ public measure(): StateSetMeasurementResult { + assert && assert( - this.measurementStateProperty.value === 'readyToBeMeasured' || this.measurementStateProperty.value === 'measuredAndRevealed', + this.measurementStateProperty.value !== 'preparingToBeMeasured', 'The system should be ready for measurement or have already been measured.' ); - _.times( this.numberOfActiveSystemsProperty.value, i => { + // If the system is ready to be measured, but hasn't yet been, do it now. + if ( this.measurementStateProperty.value === 'readyToBeMeasured' ) { - // Only make a new measurement if one doesn't exist for this element. Otherwise, just keep the existing value. - if ( this.measuredValues[ i ] === null ) { - const valueSetIndex = dotRandom.nextDouble() < this.biasProperty.value ? 0 : 1; - this.measuredValues[ i ] = this.validValues[ valueSetIndex ]; - } - } ); + this.generateNewMeasurementValues(); + + // Change the state to represent that this system has now been measured. + this.measurementStateProperty.value = 'revealed'; + } - this.measurementStateProperty.value = 'measuredAndRevealed'; return { length: this.numberOfActiveSystemsProperty.value, measuredValues: this.measuredValues }; } + private generateNewMeasurementValues(): void { + + // Generate a new seed that will subsequently be used to generate the random data. + this.seedProperty.value = dotRandom.nextDouble(); + + // Create the measured values. + const random = new Random( { seed: this.seedProperty.value } ); + _.times( this.numberOfActiveSystemsProperty.value, i => { + const valueSetIndex = random.nextDouble() < this.biasProperty.value ? 0 : 1; + this.measuredValues[ i ] = this.validValues[ valueSetIndex ]; + } ); + + // Signal that the data has been updated. + this.measuredDataChanged.emit(); + } + /** * Set the measurement value immediately for all elements in this set without transitioning through the * 'preparingToBeMeasured' state. @@ -181,38 +277,13 @@ export default class TwoStateSystemSet extends PhetioObject { _.times( this.numberOfActiveSystemsProperty.value, i => { this.measuredValues[ i ] = value; } ); - this.measurementStateProperty.value = 'readyToBeMeasured'; - } - - /** - * Go back to the 'readyToBeMeasured' state without re-preparing the measurement. - */ - public hide(): void { - this.measurementStateProperty.value = 'readyToBeMeasured'; + this.measurementStateProperty.value = this.systemType === 'classical' ? 'measuredAndHidden' : 'readyToBeMeasured'; } public reset(): void { this.measurementStateProperty.reset(); this.numberOfActiveSystemsProperty.reset(); } - - /** - * TwoStateSystemSetIO uses reference type serialization because in this sim the instances are created once when the - * sim is initialized. - */ - public static readonly TwoStateSystemSetIO = new IOType, TwoStateSystemSetStateObject>( - 'TwoStateSystemSetIO', - { - valueType: TwoStateSystemSet, - stateSchema: { - measuredValues: ArrayIO( NullableIO( StringIO ) ) - } - } - ); } -export type TwoStateSystemSetStateObject = { - measuredValues: Array; -}; - quantumMeasurement.register( 'TwoStateSystemSet', TwoStateSystemSet ); \ No newline at end of file