Skip to content

Commit

Permalink
change from "current amplitude" to "normalized current" throughout, #130
Browse files Browse the repository at this point in the history
  • Loading branch information
pixelzoom committed Apr 3, 2024
1 parent 6039614 commit 1114c27
Show file tree
Hide file tree
Showing 12 changed files with 127 additions and 114 deletions.
4 changes: 2 additions & 2 deletions doc/implementation-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,8 @@ The electromagnet is also a "Hollywood" model, based on a coil magnet. See detai

### Coil

The same coil implementation (`Coil`) is used for both the pickup coil and the electromagnet coil. The concept of "current amplitude"
is fundamental to understand the sim model. It is described in [model.md](https://github.com/phetsims/faradays-electromagnetic-lab/blob/main/doc/model.md#bar-magnet) and in `Coil`.
The same coil implementation (`Coil`) is used for both the pickup coil and the electromagnet coil. The concept of "normalized current"
is fundamental to understanding the sim model. It is described in [model.md](https://github.com/phetsims/faradays-electromagnetic-lab/blob/main/doc/model.md#bar-magnet) and in `Coil`.

The most complicated part of the sim may be `Coil.createCoilSegments`. It creates an ordered `CoilSegment[]` that describes the
shape of the coil, and the path that electrons follow as they flow through the coil. So that objects (bar magnet, compass,...) may
Expand Down
23 changes: 12 additions & 11 deletions doc/model.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ are provided, e.g. `BarMagnet`. Code can be found [here](https://github.com/phe

**B-field**: a synonym for magnetic field.

**Current amplitude**: A value with range [-1,1]. The magnitude describes the amount of current relative to the
**Normalized Current**: A value with range [-1,1]. The magnitude describes the amount of current relative to the
maximum current that may be induced in the model. The sign indicates the direction
of the current. View components use this value to determine how they should respond to induced current.
For example, deflection of the voltmeter needle, brightness of the light bulb, and speed of
Expand Down Expand Up @@ -49,17 +49,17 @@ points in these grids, scaled to match the strength of the bar magnet.

### Electromagnet
The electromagnet (see `Electromagnet`) is based on a coil magnet model. Its voltage source can be either a DC or AC power supply.
The strength of the B-field produced by the electromagnet is proportional to the amplitude of the voltage in the
voltage source and the number of loops in the coil. (The diameter of the loops is fixed.) The current amplitude in the coil
is proportional to the amplitude of the voltage source. **Note that there is no model of resistance for the coil or voltage source.**
The strength of the B-field produced by the electromagnet is proportional to the voltage in the
power supply and the number of loops in the coil. (The diameter of the loops is fixed.) The normalized current in the coil
is proportional to the voltage of the power supply. **Note that there is no model of resistance for the coil or voltage source.**

The DC power supply (aka battery) has a maximum voltage, and its voltage
amplitude and polarity is varied by the user via a slider control. See `DCPowerSuply`.
and polarity is varied by the user via a slider control. See `DCPowerSuply`.

The AC Power Supply has a configurable maximum voltage. The user varies the maximum voltage amplitude and
frequency using sliders. The voltage amplitude varies over time. See `ACPowerSupply`.

Electrons in the electromagnet's coil move at a speed and direction that is proportional to the current amplitude in
Electrons in the electromagnet's coil move at a speed and direction that is proportional to the normalized current in
the coil. More current results in faster speed. A change in polarity of the magnet results in change in direction
of the electrons.

Expand Down Expand Up @@ -100,13 +100,14 @@ The pickup coil (see `PickupCoil`) is the most complicated part of the model, an
The magnetic field is sampled
and averaged along a vertical line through the center of the coil. The average is used to compute the flux in
one loop of the coil, then multiplied by the number of loops. The flux is measured over time. A change in
flux induces an EMF, and the current amplitude is a function of the induced EMF.
flux induces an EMF, and the normalized current proportional to the induced EMF. Resistance of the coil
is constant; it does not vary with the loop area and number of loops.

The pickup coil can have one of two indicators attached to it: a light bulb (`LightBulb`) or a voltmeter (`Voltmeter`). These indicators
react to the current amplitude in the coil The light bulb’s intensity is proportional to the absolute value
of the current amplitude. The voltmeter’s needle deflection is proportional to the current amplitude, and
uses an ah hoc algorithm that makes the needle wobble around the zero point.
react to the normalized current in the coil The light bulb’s intensity is proportional to the absolute value
of the normalized current. The voltmeter’s needle deflection is proportional to the normalized current, and
uses an ah hoc algorithm that makes adds kinematic behavior to the needle.

Similar to the electromagnet coil, electrons in the pickup coil react to the current amplitude in the coil.
Similar to the electromagnet coil, electrons in the pickup coil react to the normalized current in the coil.
More current results in faster speed. A change in polarity of the magnet field results in change in direction
of the electrons.
19 changes: 12 additions & 7 deletions js/common/FELConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,13 +190,18 @@ const FELConstants = {
// Model
MAGNET_STRENGTH_RANGE: new Range( 0, 300 ), // G

// Range of currentAmplitudeProperty. The magnitude determines the relative amount of current, while the sign
// determines the current direction. See Coil currentAmplitudeProperty and
// https://github.com/phetsims/faradays-electromagnetic-lab/issues/63
CURRENT_AMPLITUDE_RANGE: new Range( -1, 1 ),

// Absolute current amplitude below this value is treated as zero.
CURRENT_AMPLITUDE_THRESHOLD: 0.001
// Range of normalizedCurrentProperty. The magnitude determines the relative amount of current, while the sign
// determines the current direction. See Coil.normalizedCurrentProperty.
NORMALIZED_CURRENT_RANGE: new Range( -1, 1 ),

// Absolute normalized current below this value is treated as zero.
NORMALIZED_CURRENT_THRESHOLD: 0.001,

// phetioDocumentation for all instances of normalizedCurrentProperty.
NORMALIZED_CURRENT_PHET_IO_DOCUMENTATION:
'For internal use only. Current in the coil is normalized to the range [-1,1]. ' +
'The magnitude indicates the relative amount of current flowing in the coil, ' +
'while the sign indicates the direction of flow.'
};

faradaysElectromagneticLab.register( 'FELConstants', FELConstants );
Expand Down
20 changes: 12 additions & 8 deletions js/common/model/Coil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,12 @@ export default class Coil extends PhetioObject {
// current relative to some maximum current in the model. The sign indicates the direction of that current. View
// components can use this value to determine how they should behave -- eg, how far to move a voltmeter needle,
// how bright to make a light bulb, and how fast to move electrons.
public readonly currentAmplitudeProperty: TReadOnlyProperty<number>;
private readonly currentAmplitudeRange: Range;
// See https://github.com/phetsims/faradays-electromagnetic-lab/issues/63
//
// In the Java version, this was named currentAmplitudeProperty. But there were objections to that name during code
// review because amplitude is not signed. See https://github.com/phetsims/faradays-electromagnetic-lab/issues/130
public readonly normalizedCurrentProperty: TReadOnlyProperty<number>;
private readonly normalizedCurrentRange: Range;

// Width of the wire that makes up the coil.
public readonly wireWidth: number;
Expand Down Expand Up @@ -116,8 +120,8 @@ export default class Coil extends PhetioObject {
// Fires after electrons have moved.
public readonly electronsMovedEmitter: Emitter;

public constructor( currentAmplitudeProperty: TReadOnlyProperty<number>, currentAmplitudeRange: Range, providedOptions: CoilOptions ) {
assert && assert( currentAmplitudeRange.equals( FELConstants.CURRENT_AMPLITUDE_RANGE ) );
public constructor( normalizedCurrentProperty: TReadOnlyProperty<number>, normalizedCurrentRange: Range, providedOptions: CoilOptions ) {
assert && assert( normalizedCurrentRange.equals( FELConstants.NORMALIZED_CURRENT_RANGE ) );

const options = optionize<CoilOptions, SelfOptions, PhetioObjectOptions>()( {

Expand All @@ -136,8 +140,8 @@ export default class Coil extends PhetioObject {

super( options );

this.currentAmplitudeProperty = currentAmplitudeProperty;
this.currentAmplitudeRange = currentAmplitudeRange;
this.normalizedCurrentProperty = normalizedCurrentProperty;
this.normalizedCurrentRange = normalizedCurrentRange;

this.wireWidth = options.wireWidth;
this.loopSpacing = options.loopSpacing;
Expand Down Expand Up @@ -219,7 +223,7 @@ export default class Coil extends PhetioObject {
public step( dt: number ): void {

// Step the electrons if there's current flow in the coil, and the electrons are visible.
if ( this.currentAmplitudeProperty.value !== 0 && this.electronsVisibleProperty.value ) {
if ( this.normalizedCurrentProperty.value !== 0 && this.electronsVisibleProperty.value ) {
this.electronsProperty.value.forEach( electron => electron.step( dt ) );
this.electronsMovedEmitter.emit();
}
Expand Down Expand Up @@ -408,7 +412,7 @@ export default class Coil extends PhetioObject {
const coilSegmentPosition = i / numberOfElectrons;

// Model
const electron = new Electron( this.currentAmplitudeProperty, this.currentAmplitudeRange, {
const electron = new Electron( this.normalizedCurrentProperty, this.normalizedCurrentRange, {
coilSegments: coilSegments,
coilSegmentIndex: coilSegmentIndex,
coilSegmentPosition: coilSegmentPosition,
Expand Down
11 changes: 6 additions & 5 deletions js/common/model/CurrentSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ export default class CurrentSource extends PhetioObject {
// Voltage that will cause current flow
public readonly voltageProperty: NumberProperty;

// Amplitude of the current, relative to the voltage. See Coil currentAmplitudeProperty.
public readonly currentAmplitudeProperty: TReadOnlyProperty<number>;
// Normalized current, relative to the voltage. See Coil normalizedCurrentProperty.
public readonly normalizedCurrentProperty: TReadOnlyProperty<number>;

protected constructor( providedOptions: CurrentSourceOptions ) {

Expand All @@ -73,9 +73,10 @@ export default class CurrentSource extends PhetioObject {
phetioFeatured: true
}, options.voltagePropertyOptions ) );

this.currentAmplitudeProperty = new DerivedProperty( [ this.voltageProperty ],
voltage => Utils.linear( voltageRange.min, voltageRange.max, FELConstants.CURRENT_AMPLITUDE_RANGE.min, FELConstants.CURRENT_AMPLITUDE_RANGE.max, voltage ), {
isValidValue: currentAmplitude => FELConstants.CURRENT_AMPLITUDE_RANGE.contains( currentAmplitude )
// Normalized current is a linear mapping from voltage. We are considering resistance to be constant.
this.normalizedCurrentProperty = new DerivedProperty( [ this.voltageProperty ],
voltage => Utils.linear( voltageRange.min, voltageRange.max, FELConstants.NORMALIZED_CURRENT_RANGE.min, FELConstants.NORMALIZED_CURRENT_RANGE.max, voltage ), {
isValidValue: normalizedCurrent => FELConstants.NORMALIZED_CURRENT_RANGE.contains( normalizedCurrent )
} );
}

Expand Down
39 changes: 20 additions & 19 deletions js/common/model/Electromagnet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,18 +59,19 @@ export default class Electromagnet extends CoilMagnet {
phetioFeatured: true
} );

// Current amplitude in the coil is equivalent to the current amplitude of the selected power supply.
// See Coil currentAmplitudeProperty for additional documentation.
const currentAmplitudeProperty = new DerivedProperty(
[ currentSourceProperty, dcPowerSupply.currentAmplitudeProperty, acPowerSupply.currentAmplitudeProperty ],
( currentSource, dcCurrentAmplitude, acCurrentAmplitude ) =>
( currentSource === dcPowerSupply ) ? dcCurrentAmplitude : acCurrentAmplitude, {
isValidValue: currentAmplitude => FELConstants.CURRENT_AMPLITUDE_RANGE.contains( currentAmplitude ),
tandem: coilTandem.createTandem( 'currentAmplitudeProperty' ),
phetioValueType: NumberIO
// Normalized current in the coil is equivalent to the normalized current produced by the selected power supply,
// See Coil normalizedCurrentProperty for additional documentation.
const normalizedCurrentProperty = new DerivedProperty(
[ currentSourceProperty, dcPowerSupply.normalizedCurrentProperty, acPowerSupply.normalizedCurrentProperty ],
( currentSource, dcNormalizedCurrent, acNormalizedCurrent ) =>
( currentSource === dcPowerSupply ) ? dcNormalizedCurrent : acNormalizedCurrent, {
isValidValue: normalizedCurrent => FELConstants.NORMALIZED_CURRENT_RANGE.contains( normalizedCurrent ),
tandem: coilTandem.createTandem( 'normalizedCurrentProperty' ),
phetioValueType: NumberIO,
phetioDocumentation: FELConstants.NORMALIZED_CURRENT_PHET_IO_DOCUMENTATION
} );

const coil = new Coil( currentAmplitudeProperty, FELConstants.CURRENT_AMPLITUDE_RANGE, {
const coil = new Coil( normalizedCurrentProperty, FELConstants.NORMALIZED_CURRENT_RANGE, {
maxLoopArea: 7854, // to match Java version
loopAreaPercentRange: new RangeWithValue( 100, 100, 100 ), // fixed loop area
numberOfLoopsRange: new RangeWithValue( 1, 4, 4 ),
Expand All @@ -80,10 +81,10 @@ export default class Electromagnet extends CoilMagnet {

// As we said in the Java version... This is a bit of a "fudge". Strength of the magnet is proportional to its EMF.
const strengthProperty = new DerivedProperty(
[ coil.numberOfLoopsProperty, coil.numberOfLoopsProperty.rangeProperty, currentAmplitudeProperty ],
( numberOfLoops, numberOfLoopsRange, currentAmplitude ) => {
const amplitude = ( numberOfLoops / numberOfLoopsRange.max ) * currentAmplitude;
return Math.abs( amplitude ) * FELConstants.MAGNET_STRENGTH_RANGE.max;
[ coil.numberOfLoopsProperty, coil.numberOfLoopsProperty.rangeProperty, normalizedCurrentProperty ],
( numberOfLoops, numberOfLoopsRange, normalizedCurrent ) => {
const amplitude = Math.abs( ( numberOfLoops / numberOfLoopsRange.max ) * normalizedCurrent );
return amplitude * FELConstants.MAGNET_STRENGTH_RANGE.max;
}, {
units: 'G',
isValidValue: strength => FELConstants.MAGNET_STRENGTH_RANGE.contains( strength ),
Expand All @@ -99,11 +100,11 @@ export default class Electromagnet extends CoilMagnet {
this.acPowerSupply = acPowerSupply;
this.currentSourceProperty = currentSourceProperty;

// Polarity is determined by the sign of the current amplitude.
assert && assert( FELConstants.CURRENT_AMPLITUDE_RANGE.min < 0 && FELConstants.CURRENT_AMPLITUDE_RANGE.max > 0,
'currentAmplitudeProperty listener assumes that range is signed' );
this.coil.currentAmplitudeProperty.link( currentAmplitude => {
this.rotationProperty.value = ( currentAmplitude >= 0 ) ? 0 : Math.PI;
// Polarity is determined by the sign of the normalized current.
assert && assert( FELConstants.NORMALIZED_CURRENT_RANGE.min < 0 && FELConstants.NORMALIZED_CURRENT_RANGE.max > 0,
'normalizedCurrentProperty listener assumes that range is signed' );
this.coil.normalizedCurrentProperty.link( normalizedCurrent => {
this.rotationProperty.value = ( normalizedCurrent >= 0 ) ? 0 : Math.PI;
} );

this.shapeVisibleProperty = new BooleanProperty( false
Expand Down
24 changes: 12 additions & 12 deletions js/common/model/Electron.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ type ElectronOptions = SelfOptions;

export default class Electron {

// Amplitude of the current that this electron represents.
private readonly currentAmplitudeProperty: TReadOnlyProperty<number>;
private readonly currentAmplitudeRange: Range;
// Current that this electron represents.
private readonly normalizedCurrentProperty: TReadOnlyProperty<number>;
private readonly normalizedCurrentRange: Range;

// Electron's position, relative to the coil's position. This Vector2 is mutated as position changes.
private readonly position: Vector2;
Expand All @@ -71,13 +71,13 @@ export default class Electron {
// Scale for adjusting speed.
private readonly speedScaleProperty: TReadOnlyProperty<number>;

public constructor( currentAmplitudeProperty: TReadOnlyProperty<number>, currentAmplitudeRange: Range, providedOptions: ElectronOptions ) {
public constructor( normalizedCurrentProperty: TReadOnlyProperty<number>, normalizedCurrentRange: Range, providedOptions: ElectronOptions ) {

const options = providedOptions;
assert && assert( COIL_SEGMENT_POSITION_RANGE.contains( options.coilSegmentPosition ) );

this.currentAmplitudeProperty = currentAmplitudeProperty;
this.currentAmplitudeRange = currentAmplitudeRange;
this.normalizedCurrentProperty = normalizedCurrentProperty;
this.normalizedCurrentRange = normalizedCurrentRange;

const coilSegment = options.coilSegments[ options.coilSegmentIndex ];

Expand Down Expand Up @@ -130,7 +130,7 @@ export default class Electron {
public step( dt: number ): void {
assert && assert( dt === ConstantDtClock.DT, `invalid dt=${dt}` );

this.speedAndDirection = this.currentAmplitudeToSpeedAndDirection( this.currentAmplitudeProperty.value );
this.speedAndDirection = this.normalizedCurrentToSpeedAndDirection( this.normalizedCurrentProperty.value );

if ( this.speedAndDirection !== 0 ) {

Expand Down Expand Up @@ -158,13 +158,13 @@ export default class Electron {
}

/**
* Maps current amplitude in the coil to the electron's speed and direction.
* Maps normalized current in the coil to the electron's speed and direction.
*/
private currentAmplitudeToSpeedAndDirection( currentAmplitude: number ): number {
private normalizedCurrentToSpeedAndDirection( normalizedCurrent: number ): number {
let speedAndDirection = 0;
if ( Math.abs( currentAmplitude ) > FELConstants.CURRENT_AMPLITUDE_THRESHOLD ) {
speedAndDirection = Utils.linear( this.currentAmplitudeRange.min, this.currentAmplitudeRange.max,
this.speedAndDirectionRange.min, this.speedAndDirectionRange.max, currentAmplitude );
if ( Math.abs( normalizedCurrent ) > FELConstants.NORMALIZED_CURRENT_THRESHOLD ) {
speedAndDirection = Utils.linear( this.normalizedCurrentRange.min, this.normalizedCurrentRange.max,
this.speedAndDirectionRange.min, this.speedAndDirectionRange.max, normalizedCurrent );
}
return speedAndDirection;
}
Expand Down
Loading

0 comments on commit 1114c27

Please sign in to comment.