Skip to content

Commit

Permalink
start adding support for timed measurements, see #54
Browse files Browse the repository at this point in the history
  • Loading branch information
jbphet committed Jan 9, 2025
1 parent 34253ff commit a47b0bc
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 34 deletions.
65 changes: 49 additions & 16 deletions js/bloch-sphere/model/BlochSphereModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,20 @@ import { EmptySelfOptions } from '../../../../phet-core/js/optionize.js';
import PickRequired from '../../../../phet-core/js/types/PickRequired.js';
import { PhetioObjectOptions } from '../../../../tandem/js/PhetioObject.js';
import EnumerationIO from '../../../../tandem/js/types/EnumerationIO.js';
import StringUnionIO from '../../../../tandem/js/types/StringUnionIO.js';
import QuantumMeasurementConstants from '../../common/QuantumMeasurementConstants.js';
import quantumMeasurement from '../../quantumMeasurement.js';
import ComplexBlochSphere from './ComplexBlochSphere.js';
import { MeasurementBasis } from './MeasurementBasis.js';
import { SpinMeasurementState, SpinMeasurementStateValues } from './SpinMeasurementState.js';
import { StateDirection } from './StateDirection.js';

type SelfOptions = EmptySelfOptions;

type QuantumMeasurementModelOptions = SelfOptions & PickRequired<PhetioObjectOptions, 'tandem'>;

// constants
const MAX_OBSERVATION_TIME = 2 * Math.PI / QuantumMeasurementConstants.MAX_PRECESSION_RATE;

export default class BlochSphereModel implements TModel {

public readonly showMagneticFieldProperty: BooleanProperty;
Expand Down Expand Up @@ -61,7 +66,7 @@ export default class BlochSphereModel implements TModel {

// A flag that indicates whether the model is ready to observe or needs the state to be prepared. This should not be
// modified directly by client code, but rather by the model's observe() and reprepare() methods.
public readonly readyToObserveProperty: BooleanProperty;
public readonly measurementStateProperty: Property<SpinMeasurementState>;

// Properties for the spin measurements made.
public readonly upMeasurementCountProperty: NumberProperty;
Expand Down Expand Up @@ -115,9 +120,9 @@ export default class BlochSphereModel implements TModel {
range: new Range( -1, 1 )
} );

this.timeToMeasurementProperty = new NumberProperty( 0, {
this.timeToMeasurementProperty = new NumberProperty( MAX_OBSERVATION_TIME / 2, {
tandem: providedOptions.tandem.createTandem( 'timeToMeasurementProperty' ),
range: new Range( 0, 1 )
range: new Range( 0, MAX_OBSERVATION_TIME )
} );

this.measurementTimeProperty = new NumberProperty( 0, {
Expand Down Expand Up @@ -156,9 +161,11 @@ export default class BlochSphereModel implements TModel {
}
} );

this.readyToObserveProperty = new BooleanProperty( true, {
this.measurementStateProperty = new Property<SpinMeasurementState>( 'prepared', {
phetioReadOnly: true,
tandem: providedOptions.tandem.createTandem( 'readyToObserveProperty' )
phetioValueType: StringUnionIO( SpinMeasurementStateValues ),
validValues: SpinMeasurementStateValues,
tandem: providedOptions.tandem.createTandem( 'measurementStateProperty' )
} );

Multilink.multilink(
Expand Down Expand Up @@ -186,8 +193,8 @@ export default class BlochSphereModel implements TModel {
0;
this.multiMeasurementBlochSpheres.forEach( blochSphere => {
blochSphere.rotatingSpeedProperty.value = showMagneticField ?
magneticFieldStrength :
0;
magneticFieldStrength :
0;
} );
}
);
Expand All @@ -214,8 +221,8 @@ export default class BlochSphereModel implements TModel {
/**
* Make whatever observation ths mode is currently set up to make.
*/
public observe(): void {
if ( this.readyToObserveProperty.value ) {
private observe(): void {
if ( this.measurementStateProperty.value === 'prepared' ) {

if ( this.isSingleMeasurementModeProperty.value ) {
this.singleMeasurementBlochSphere.measure( this.measurementBasisProperty.value, this.upMeasurementCountProperty, this.downMeasurementCountProperty );
Expand All @@ -227,15 +234,37 @@ export default class BlochSphereModel implements TModel {
}

// Update the measurement state.
this.readyToObserveProperty.value = false;
this.measurementStateProperty.value = 'observed';
}
}

/**
* Initiates an observation, aka a measurement, of the spin value or values. If the model is in the state where
* precession is occurring, this starts a timer that will trigger the measurement when it expires. If precession is
* not occurring, the measurement is made immediately.
*/
public initiateObservation(): void {

this.reprepare();

if ( this.showMagneticFieldProperty.value ) {

// Transition to the state where the model is waiting to take a measurement.
this.measurementStateProperty.value = 'timingObservation';
this.measurementTimeProperty.value = 0;
}
else {

// Make the measurement immediately.
this.observe();
}
}

/**
* Reprepare the model for a new observation.
*/
public reprepare(): void {
this.readyToObserveProperty.value = true;
this.measurementStateProperty.value = 'prepared';

// Copy the settings from the preparation bloch sphere
this.singleMeasurementBlochSphere.setDirection(
Expand Down Expand Up @@ -266,7 +295,7 @@ export default class BlochSphereModel implements TModel {
this.resetCounts();
this.preparationBlochSphere.reset();
this.showMagneticFieldProperty.reset();
this.readyToObserveProperty.reset();
this.measurementStateProperty.reset();
this.magneticFieldStrengthProperty.reset();
this.measurementBasisProperty.reset();
this.isSingleMeasurementModeProperty.reset();
Expand All @@ -282,9 +311,13 @@ export default class BlochSphereModel implements TModel {
this.multiMeasurementBlochSpheres.forEach( blochSphere => {
blochSphere.step( dt );
} );
if ( this.showMagneticFieldProperty.value ) {
this.measurementTimeProperty.value += dt;
this.measurementTimeProperty.value %= 2;

if ( this.measurementStateProperty.value === 'timingObservation' ) {
this.measurementTimeProperty.value = this.measurementTimeProperty.value + dt;
if ( this.measurementTimeProperty.value > this.timeToMeasurementProperty.value ) {
this.reprepare();
this.observe();
}
}
}
}
Expand Down
22 changes: 22 additions & 0 deletions js/bloch-sphere/model/SpinMeasurementState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright 2024, University of Colorado Boulder

/**
* SpinMeasurementStates is a union type the represents the possible states for the measurement of a quantum spin system.
*
* @author John Blanco (PhET Interactive Simulations)
*/

export const SpinMeasurementStateValues = [

// The system is prepared for a measurement.
'prepared',

// The system is prepared for a measurement and a timer is running, and when the timer expires, the measurement will
// be made.
'timingObservation',

// The spin of the system has been observed.
'observed'

] as const;
export type SpinMeasurementState = ( typeof SpinMeasurementStateValues )[number];
11 changes: 7 additions & 4 deletions js/bloch-sphere/view/BlochSphereMeasurementArea.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,26 +159,29 @@ export default class BlochSphereMeasurementArea extends Node {

const prepareObserveButtonTextProperty = new DerivedStringProperty(
[
model.readyToObserveProperty,
model.measurementStateProperty,
QuantumMeasurementStrings.observeStringProperty,
QuantumMeasurementStrings.reprepareStringProperty
],
( readyToObserve, observeString, reprepareString ) => readyToObserve ? observeString : reprepareString
( measurementState, observeString, reprepareString ) => measurementState === 'observed' ?
reprepareString :
observeString
);

const prepareObserveButton = new TextPushButton(
prepareObserveButtonTextProperty,
{
listener: () => {
if ( model.readyToObserveProperty.value ) {
model.observe();
if ( model.measurementStateProperty.value === 'prepared' ) {
model.initiateObservation();
}
else {
model.reprepare();
}
},
baseColor: QuantumMeasurementColors.experimentButtonColorProperty,
font: new PhetFont( 18 ),
enabledProperty: DerivedProperty.valueNotEqualsConstant( model.measurementStateProperty, 'timingObservation' ),
xMargin: 20,
yMargin: 6,
maxWidth: measurementControlPanel.width,
Expand Down
29 changes: 16 additions & 13 deletions js/bloch-sphere/view/MeasurementTimerControl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* Bloch Sphere is measured.
*
* @author Agustín Vallejo
* @author John Blanco (PhET Interactive Simulations)
*/

import NumberProperty from '../../../../axon/js/NumberProperty.js';
Expand All @@ -25,16 +26,18 @@ import quantumMeasurement from '../../quantumMeasurement.js';
type SelfOptions = EmptySelfOptions;
export type MeasurementTimerControlOptions = SelfOptions & WithRequired<PanelOptions, 'tandem'>;

// constants
const SLIDER_TRACK_SIZE = new Dimension2( 150, 0.1 );

export default class MeasurementTimerControl extends Node {

public constructor(
timeToMeasurementProperty: NumberProperty,
measurementTimeProperty: NumberProperty,
providedOptions: MeasurementTimerControlOptions
) {
public constructor( timeToMeasurementProperty: NumberProperty,
measurementTimeProperty: NumberProperty,
providedOptions: MeasurementTimerControlOptions ) {

// TODO: This should probably be rewritten to use thumbNode. Seems like that would be way simpler. See https://github.com/phetsims/quantum-measurement/issues/54.

const maxMeasurementTime = timeToMeasurementProperty.rangeProperty.value.max;
const thumbOffset = 30;
const thumbDimensions = new Dimension2( 30, 30 );
const timeToMeasurementSlider = new Slider( timeToMeasurementProperty, timeToMeasurementProperty.range, {
Expand All @@ -52,11 +55,11 @@ export default class MeasurementTimerControl extends Node {
minorTickLength: 5
} );
timeToMeasurementSlider.addMajorTick( 0, new Text( '0', { font: new PhetFont( 15 ) } ) );
timeToMeasurementSlider.addMajorTick( 1, new Text( 't', { font: new PhetFont( 15 ) } ) );
timeToMeasurementSlider.addMinorTick( 0.2 );
timeToMeasurementSlider.addMinorTick( 0.4 );
timeToMeasurementSlider.addMinorTick( 0.6 );
timeToMeasurementSlider.addMinorTick( 0.8 );
timeToMeasurementSlider.addMajorTick( maxMeasurementTime, new Text( 't', { font: new PhetFont( 15 ) } ) );
timeToMeasurementSlider.addMinorTick( 0.2 * maxMeasurementTime );
timeToMeasurementSlider.addMinorTick( 0.4 * maxMeasurementTime );
timeToMeasurementSlider.addMinorTick( 0.6 * maxMeasurementTime );
timeToMeasurementSlider.addMinorTick( 0.8 * maxMeasurementTime );

const timeIndicatorScale = 15;
const timeIndicator = new ArrowNode( 0, timeIndicatorScale, 0, 0, {
Expand All @@ -68,7 +71,7 @@ export default class MeasurementTimerControl extends Node {
} );

measurementTimeProperty.link( measurementTime => {
timeIndicator.centerX = measurementTime / 2 * SLIDER_TRACK_SIZE.width;
timeIndicator.centerX = measurementTime / timeToMeasurementProperty.rangeProperty.value.max * SLIDER_TRACK_SIZE.width;
} );

const thumbLine = new Path( new Shape().circle( 0, 0, 2 ).moveTo( 0, 0 ).lineTo( 0, thumbOffset - thumbDimensions.height / 2 ), {
Expand Down Expand Up @@ -101,10 +104,10 @@ export default class MeasurementTimerControl extends Node {
} );

timeToMeasurementProperty.link( time => {
thumbLine.centerX = time * SLIDER_TRACK_SIZE.width;
thumbLine.centerX = time / maxMeasurementTime * SLIDER_TRACK_SIZE.width;
thumbLine.top = -2;

measurementSymbol.centerX = time * SLIDER_TRACK_SIZE.width;
measurementSymbol.centerX = time / maxMeasurementTime * SLIDER_TRACK_SIZE.width;
measurementSymbol.centerY = thumbOffset / 2 + thumbDimensions.height / 2;
} );

Expand Down
2 changes: 1 addition & 1 deletion js/common/QuantumMeasurementConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const QuantumMeasurementConstants = {
KET: '\u27e9',
HBAR: '\u210F',

MAX_PRECESSION_RATE: Math.PI, // radians per second
MAX_PRECESSION_RATE: Math.PI, // in radians per second

expectedPercentagePathOptions: {
stroke: '#0a0',
Expand Down

0 comments on commit a47b0bc

Please sign in to comment.