From f94055de456953f72c747bfe2a7dfa6db90c95f9 Mon Sep 17 00:00:00 2001 From: jbphet Date: Wed, 15 Jan 2025 10:40:26 -0700 Subject: [PATCH] add system-under-test area to 4th screen, see https://github.com/phetsims/quantum-measurement/issues/54 --- js/bloch-sphere/model/BlochSphereModel.ts | 12 +- .../view/BlochSphereMeasurementArea.ts | 66 ++++++++--- js/bloch-sphere/view/BlochSphereScreenView.ts | 25 +--- js/bloch-sphere/view/MagneticFieldControl.ts | 2 +- js/bloch-sphere/view/MagneticFieldNode.ts | 27 +++-- js/bloch-sphere/view/SystemUnderTestNode.ts | 108 ++++++++++++++++++ 6 files changed, 185 insertions(+), 55 deletions(-) create mode 100644 js/bloch-sphere/view/SystemUnderTestNode.ts diff --git a/js/bloch-sphere/model/BlochSphereModel.ts b/js/bloch-sphere/model/BlochSphereModel.ts index 0da9c09..3904df7 100644 --- a/js/bloch-sphere/model/BlochSphereModel.ts +++ b/js/bloch-sphere/model/BlochSphereModel.ts @@ -34,7 +34,7 @@ const MAX_OBSERVATION_TIME = 2 * Math.PI / QuantumMeasurementConstants.MAX_PRECE class BlochSphereModel implements TModel { - public readonly showMagneticFieldProperty: BooleanProperty; + public readonly magneticFieldEnabledProperty: BooleanProperty; // Bloch Spheres shown in the screen public readonly preparationBlochSphere: ComplexBlochSphere; @@ -75,8 +75,8 @@ class BlochSphereModel implements TModel { public constructor( providedOptions: QuantumMeasurementModelOptions ) { - this.showMagneticFieldProperty = new BooleanProperty( false, { - tandem: providedOptions.tandem.createTandem( 'showMagneticFieldProperty' ), + this.magneticFieldEnabledProperty = new BooleanProperty( false, { + tandem: providedOptions.tandem.createTandem( 'magneticFieldEnabledProperty' ), phetioFeatured: true } ); @@ -192,7 +192,7 @@ class BlochSphereModel implements TModel { // Set the precession rate of the Bloch sphere based on the magnetic field strength and the selected scene. Multilink.multilink( - [ this.magneticFieldStrengthProperty, this.showMagneticFieldProperty ], + [ this.magneticFieldStrengthProperty, this.magneticFieldEnabledProperty ], ( magneticFieldStrength, showMagneticField ) => { this.singleMeasurementBlochSphere.rotatingSpeedProperty.value = showMagneticField ? magneticFieldStrength : @@ -267,7 +267,7 @@ class BlochSphereModel implements TModel { 'The model should be prepared for measurement prior to calling this method.' ); - if ( this.showMagneticFieldProperty.value ) { + if ( this.magneticFieldEnabledProperty.value ) { // Transition to the state where the model is waiting to take a measurement. this.measurementStateProperty.value = 'timingObservation'; @@ -316,7 +316,7 @@ class BlochSphereModel implements TModel { public reset(): void { this.resetCounts(); this.preparationBlochSphere.reset(); - this.showMagneticFieldProperty.reset(); + this.magneticFieldEnabledProperty.reset(); this.measurementStateProperty.reset(); this.magneticFieldStrengthProperty.reset(); this.measurementBasisProperty.reset(); diff --git a/js/bloch-sphere/view/BlochSphereMeasurementArea.ts b/js/bloch-sphere/view/BlochSphereMeasurementArea.ts index 681fd33..0e7739a 100644 --- a/js/bloch-sphere/view/BlochSphereMeasurementArea.ts +++ b/js/bloch-sphere/view/BlochSphereMeasurementArea.ts @@ -1,8 +1,8 @@ // Copyright 2025, University of Colorado Boulder /** - * BlochSphereMeasurementArea is the node that contains the measurement area for the Bloch Sphere screen. It contains the - * UI elements for controlling magnetic field and basis of measurements... + * BlochSphereMeasurementArea is the node that contains the measurement area for the Bloch Sphere screen. It contains + * the UI elements for controlling various aspects of the measurement that is to be performed. * * @author Agustín Vallejo * @author John Blanco (PhET Interactive Simulations) @@ -10,13 +10,15 @@ import DerivedProperty from '../../../../axon/js/DerivedProperty.js'; import DerivedStringProperty from '../../../../axon/js/DerivedStringProperty.js'; +import Dimension2 from '../../../../dot/js/Dimension2.js'; import optionize, { EmptySelfOptions } from '../../../../phet-core/js/optionize.js'; import WithRequired from '../../../../phet-core/js/types/WithRequired.js'; import PhetFont from '../../../../scenery-phet/js/PhetFont.js'; -import { Node, NodeOptions, RichText, RichTextOptions, Text, VBox } from '../../../../scenery/js/imports.js'; +import { HBox, Node, NodeOptions, RichText, RichTextOptions, Text, VBox } from '../../../../scenery/js/imports.js'; import AquaRadioButtonGroup from '../../../../sun/js/AquaRadioButtonGroup.js'; import RectangularRadioButtonGroup from '../../../../sun/js/buttons/RectangularRadioButtonGroup.js'; import TextPushButton from '../../../../sun/js/buttons/TextPushButton.js'; +import Checkbox from '../../../../sun/js/Checkbox.js'; import Panel from '../../../../sun/js/Panel.js'; import QuantumMeasurementColors from '../../common/QuantumMeasurementColors.js'; import QuantumMeasurementConstants from '../../common/QuantumMeasurementConstants.js'; @@ -28,8 +30,8 @@ import BlochSphereModel from '../model/BlochSphereModel.js'; import { MeasurementBasis } from '../model/MeasurementBasis.js'; import BlochSphereNumericalEquationNode from './BlochSphereNumericalEquationNode.js'; import MagneticFieldControl from './MagneticFieldControl.js'; -import MagneticFieldNode from './MagneticFieldNode.js'; import MeasurementTimerControl from './MeasurementTimerControl.js'; +import SystemUnderTestNode from './SystemUnderTestNode.js'; type SelfOptions = EmptySelfOptions; @@ -40,12 +42,7 @@ export default class BlochSphereMeasurementArea extends Node { public constructor( model: BlochSphereModel, providedOptions: BlochSphereMeasurementAreaOptions ) { const equationNode = new BlochSphereNumericalEquationNode( model, { - tandem: providedOptions.tandem.createTandem( 'equationNode' ), - centerY: -50 - } ); - - const magneticFieldNode = new MagneticFieldNode( model.magneticFieldStrengthProperty, { - visibleProperty: model.showMagneticFieldProperty + tandem: providedOptions.tandem.createTandem( 'equationNode' ) } ); const singleMeasurementBlochSphereNode = new BlochSphereNode( model.singleMeasurementBlochSphere, { @@ -53,7 +50,8 @@ export default class BlochSphereMeasurementArea extends Node { drawTitle: false, drawKets: false, drawAngleIndicators: true, - center: magneticFieldNode.center, + top: equationNode.bottom + 50, + left: equationNode.left, visibleProperty: model.isSingleMeasurementModeProperty } ); @@ -83,7 +81,8 @@ export default class BlochSphereMeasurementArea extends Node { currentRow++; } } ); - multipleMeasurementBlochSpheresNode.center = magneticFieldNode.center.plusXY( 0, 30 ); + multipleMeasurementBlochSpheresNode.centerX = singleMeasurementBlochSphereNode.centerX; + multipleMeasurementBlochSpheresNode.top = 70; const spinUpLabelStringProperty = new DerivedStringProperty( [ model.measurementBasisProperty ], @@ -122,6 +121,7 @@ export default class BlochSphereMeasurementArea extends Node { font: new PhetFont( 18 ), fill: 'black' }; + // Create and add the radio buttons that select the chart type view in the nuclideChartAccordionBox. const basisRadioButtonGroupTandem = providedOptions.tandem.createTandem( 'basisRadioButtonGroup' ); @@ -151,7 +151,7 @@ export default class BlochSphereMeasurementArea extends Node { const measurementTimerControl = new MeasurementTimerControl( model.timeToMeasurementProperty, model.measurementTimeProperty, { tandem: providedOptions.tandem.createTandem( 'measurementTimerControl' ), - visibleProperty: model.showMagneticFieldProperty + visibleProperty: model.magneticFieldEnabledProperty } ); const measurementControlPanel = new Panel( new VBox( { @@ -200,7 +200,7 @@ export default class BlochSphereMeasurementArea extends Node { const measurementControls = new VBox( { left: singleMeasurementBlochSphereNode.right + 20, - top: magneticFieldNode.top - 50, + top: equationNode.bottom + 10, spacing: 10, children: [ measurementResultHistogram, @@ -209,21 +209,49 @@ export default class BlochSphereMeasurementArea extends Node { ] } ); + const magneticFieldCheckbox = new Checkbox( + model.magneticFieldEnabledProperty, + // TODO: This text should be localized, see https://github.com/phetsims/quantum-measurement/issues/54 + new Text( 'Enable Magnetic Field', { font: new PhetFont( { size: 16 } ) } ), + { + spacing: 10, + centerX: multipleMeasurementBlochSpheresNode.centerX, + bottom: QuantumMeasurementConstants.LAYOUT_BOUNDS.bottom - 55, + tandem: providedOptions.tandem.createTandem( 'magneticFieldCheckbox' ) + } + ); + const magneticFieldControl = new MagneticFieldControl( model.magneticFieldStrengthProperty, { - centerX: singleMeasurementBlochSphereNode.centerX, - top: magneticFieldNode.bottom + 10, - visibleProperty: model.showMagneticFieldProperty, + visibleProperty: model.magneticFieldEnabledProperty, tandem: providedOptions.tandem.createTandem( 'magneticFieldControl' ) } ); + const systemUnderTestNode = new SystemUnderTestNode( + new Dimension2( 150, 180 ), + model.magneticFieldEnabledProperty, + model.magneticFieldStrengthProperty, + model.isSingleMeasurementModeProperty + ); + + const magneticFieldAndStrengthControl = new HBox( { + children: [ magneticFieldControl, systemUnderTestNode ], + spacing: 12, + centerX: singleMeasurementBlochSphereNode.centerX, + bottom: magneticFieldCheckbox.top - 20 + } ); + + magneticFieldAndStrengthControl.localBoundsProperty.link( () => { + magneticFieldAndStrengthControl.centerX = singleMeasurementBlochSphereNode.centerX; + } ); + const options = optionize()( { children: [ equationNode, - magneticFieldNode, singleMeasurementBlochSphereNode, multipleMeasurementBlochSpheresNode, measurementControls, - magneticFieldControl + magneticFieldAndStrengthControl, + magneticFieldCheckbox ] }, providedOptions ); diff --git a/js/bloch-sphere/view/BlochSphereScreenView.ts b/js/bloch-sphere/view/BlochSphereScreenView.ts index d20fd94..2b23e5b 100644 --- a/js/bloch-sphere/view/BlochSphereScreenView.ts +++ b/js/bloch-sphere/view/BlochSphereScreenView.ts @@ -7,16 +7,14 @@ */ import BlochSphereModel from 'model/BlochSphereModel.js'; -import PhetFont from '../../../../scenery-phet/js/PhetFont.js'; -import { Color, Line, Text } from '../../../../scenery/js/imports.js'; -import Checkbox from '../../../../sun/js/Checkbox.js'; +import { Color, Line } from '../../../../scenery/js/imports.js'; import Tandem from '../../../../tandem/js/Tandem.js'; import QuantumMeasurementScreenView from '../../common/view/QuantumMeasurementScreenView.js'; import quantumMeasurement from '../../quantumMeasurement.js'; import BlochSphereMeasurementArea from './BlochSphereMeasurementArea.js'; import BlochSpherePreparationArea from './BlochSpherePreparationArea.js'; -export default class BlochSphereScreenView extends QuantumMeasurementScreenView { +class BlochSphereScreenView extends QuantumMeasurementScreenView { private readonly model: BlochSphereModel; @@ -42,28 +40,15 @@ export default class BlochSphereScreenView extends QuantumMeasurementScreenView } ); this.addChild( dividingLine ); - const showMagneticFieldCheckbox = new Checkbox( - model.showMagneticFieldProperty, - new Text( 'Show Magnetic Field', { font: new PhetFont( { size: 16 } ) } ), - { - tandem: tandem.createTandem( 'showMagneticFieldCheckbox' ), - spacing: 10, - centerX: this.layoutBounds.centerX + 150, - top: this.layoutBounds.top + 20 - } ); - this.addChild( showMagneticFieldCheckbox ); - - const measurementArea = new BlochSphereMeasurementArea( model, { tandem: tandem.createTandem( 'measurementArea' ), left: dividingLineX + 20, - top: showMagneticFieldCheckbox.bottom + 20 + top: this.layoutBounds.top + 40 } ); this.addChild( measurementArea ); this.pdomPlayAreaNode.pdomOrder = [ preparationArea, - showMagneticFieldCheckbox, measurementArea ]; @@ -78,4 +63,6 @@ export default class BlochSphereScreenView extends QuantumMeasurementScreenView } } -quantumMeasurement.register( 'BlochSphereScreenView', BlochSphereScreenView ); \ No newline at end of file +quantumMeasurement.register( 'BlochSphereScreenView', BlochSphereScreenView ); + +export default BlochSphereScreenView; \ No newline at end of file diff --git a/js/bloch-sphere/view/MagneticFieldControl.ts b/js/bloch-sphere/view/MagneticFieldControl.ts index c5d9460..a54e669 100644 --- a/js/bloch-sphere/view/MagneticFieldControl.ts +++ b/js/bloch-sphere/view/MagneticFieldControl.ts @@ -37,7 +37,7 @@ export default class MagneticFieldControl extends Panel { fill: QuantumMeasurementColors.controlPanelFillColorProperty, stroke: null, xMargin: 10, - yMargin: 10 + yMargin: 20 }, providedOptions ); const magneticFieldIndicator = new Node( { diff --git a/js/bloch-sphere/view/MagneticFieldNode.ts b/js/bloch-sphere/view/MagneticFieldNode.ts index 289e835..673f316 100644 --- a/js/bloch-sphere/view/MagneticFieldNode.ts +++ b/js/bloch-sphere/view/MagneticFieldNode.ts @@ -6,32 +6,39 @@ */ import NumberProperty from '../../../../axon/js/NumberProperty.js'; +import Dimension2 from '../../../../dot/js/Dimension2.js'; +import { combineOptions } from '../../../../phet-core/js/optionize.js'; import { Node, NodeOptions } from '../../../../scenery/js/imports.js'; import quantumMeasurement from '../../quantumMeasurement.js'; import MagneticFieldArrowNode from './MagneticFieldArrowNode.js'; -export default class MagneticFieldNode extends Node { +const SIZE = new Dimension2( 150, 170 ); - public constructor( magneticFieldStrength: NumberProperty, providedOptions: NodeOptions ) { +export default class MagneticFieldNode extends Node { - super( providedOptions ); + public constructor( magneticFieldStrength: NumberProperty, providedOptions?: NodeOptions ) { - const columns = 8; - const rows = 7; - const separationX = 300 / columns; - const separationY = 300 / rows; + const columns = 5; + const rows = 5; + const separationX = SIZE.width / columns; + const separationY = SIZE.height / rows; + const arrowNodes: MagneticFieldArrowNode[] = []; for ( let i = 0; i < columns; i++ ) { for ( let j = 0; j < rows; j++ ) { - const magneticFieldArrowNode = new MagneticFieldArrowNode( magneticFieldStrength, 30 ); - magneticFieldStrength.link( strength => { + const magneticFieldArrowNode = new MagneticFieldArrowNode( magneticFieldStrength, 20 ); + magneticFieldStrength.link( () => { magneticFieldArrowNode.centerX = i * separationX; magneticFieldArrowNode.centerY = j * separationY; } ); - this.addChild( magneticFieldArrowNode ); + arrowNodes.push( magneticFieldArrowNode ); } } + + const options = combineOptions( { children: arrowNodes }, providedOptions ); + + super( options ); } } diff --git a/js/bloch-sphere/view/SystemUnderTestNode.ts b/js/bloch-sphere/view/SystemUnderTestNode.ts new file mode 100644 index 0000000..c9052e4 --- /dev/null +++ b/js/bloch-sphere/view/SystemUnderTestNode.ts @@ -0,0 +1,108 @@ +// Copyright 2024, University of Colorado Boulder + +/** + * + * + * @author John Blanco, PhET Interactive Simulations + */ + +import DerivedProperty from '../../../../axon/js/DerivedProperty.js'; +import NumberProperty from '../../../../axon/js/NumberProperty.js'; +import TReadOnlyProperty from '../../../../axon/js/TReadOnlyProperty.js'; +import Dimension2 from '../../../../dot/js/Dimension2.js'; +import { combineOptions } from '../../../../phet-core/js/optionize.js'; +import PhetFont from '../../../../scenery-phet/js/PhetFont.js'; +import ShadedSphereNode from '../../../../scenery-phet/js/ShadedSphereNode.js'; +import { Color, GridBox, Node, NodeOptions, Rectangle, Text, VBox } from '../../../../scenery/js/imports.js'; +import quantumMeasurement from '../../quantumMeasurement.js'; +import MagneticFieldNode from './MagneticFieldNode.js'; + +const ATOM_RADIUS = 18; // in view coordinates +const ATOM_NODE_OPTIONS = { + mainColor: Color.RED, + highlightColor: Color.RED.colorUtilsBrighter( 0.7 ) +}; +const LABEL_FONT = new PhetFont( 18 ); + +class SystemUnderTestNode extends Node { + + /** + * @param size - the size of the node in view coordinates + * @param magneticFieldEnabledProperty - whether the magnetic field is enabled + * @param magneticFieldStrengthProperty - the property that indicates the magnetic field strength + * @param isSingleMeasurementModeProperty - whether the system is in single measurement mode + * @param providedOptions - options for the node, mostly used for positioning if at all + */ + public constructor( size: Dimension2, + magneticFieldEnabledProperty: TReadOnlyProperty, + magneticFieldStrengthProperty: NumberProperty, + isSingleMeasurementModeProperty: TReadOnlyProperty, + providedOptions?: NodeOptions ) { + + const rect = new Rectangle( 0, 0, size.width, size.height, { + stroke: Color.BLACK, + fill: new Color( 235, 255, 235 ), + cornerRadius: 8 + } ); + + const magneticFieldNode = new MagneticFieldNode( magneticFieldStrengthProperty, { + center: rect.center, + opacity: 0.25, + visibleProperty: magneticFieldEnabledProperty + } ); + + const singleSphericalAtomNode = new ShadedSphereNode( ATOM_RADIUS, ATOM_NODE_OPTIONS ); + + const labeledSingleAtomNode = new VBox( { + children: [ + singleSphericalAtomNode, + new Text( 'Atom', { font: LABEL_FONT } ) + ], + center: rect.center, + visibleProperty: isSingleMeasurementModeProperty + } ); + + // Create the set of atoms for the multiple measurement mode. The layout here is quite specific and will need to + // be adjusted if the number of atoms changes. + const atomNodesForMultipleMode: Array> = []; + const numColumns = 3; + const numRows = 4; + for ( let row = 0; row < numRows; row++ ) { + for ( let column = 0; column < numColumns; column++ ) { + if ( !atomNodesForMultipleMode[ row ] ) { + atomNodesForMultipleMode[ row ] = []; + } + if ( row <= 2 || column === 1 ) { + atomNodesForMultipleMode[ row ][ column ] = new ShadedSphereNode( ATOM_RADIUS, ATOM_NODE_OPTIONS ); + } + else { + atomNodesForMultipleMode[ row ][ column ] = null; + } + } + } + const multipleSphericalAtomNode = new GridBox( { + rows: atomNodesForMultipleMode, + spacing: 10, + center: rect.center + } ); + + const labeledMultiAtomNode = new VBox( { + children: [ + multipleSphericalAtomNode, + new Text( 'Atoms', { font: LABEL_FONT } ) + ], + center: rect.center, + visibleProperty: DerivedProperty.valueEqualsConstant( isSingleMeasurementModeProperty, false ) + } ); + + const options = combineOptions( { + children: [ rect, magneticFieldNode, labeledSingleAtomNode, labeledMultiAtomNode ] + }, providedOptions ); + + super( options ); + } +} + +quantumMeasurement.register( 'SystemUnderTestNode', SystemUnderTestNode ); + +export default SystemUnderTestNode; \ No newline at end of file