Closed pixelzoom closed 1 year ago
Here's my specific problem. In WavePacket.js, I have 2 Properties: standardDeviationProperty
and inverseStandardDeviationProperty
. The relationship is:
inverseStandard = 1 / standardDeviation
... and both Properties need to be editable, in the sim's UI and in Studio. I'm currently using 2 NumberProperty instances, and handling my own synchronization.
Here's where I got to with DynamicProperty before bailing. The TODO comments indicate where I got stuck.
// @public
this.standardDeviationProperty = new NumberProperty( 3 * Math.PI, {
range: new Range( 1, 4 * Math.PI ),
tandem: options.tandem.createTandem( 'standardDeviationProperty' ),
phetioDocumentation: 'Standard deviation, a measure of the wave packet width. ' +
'In the space domain, this is \u03c3<sub>k</sub>, in rad/m. ' +
'In the time domain, this is \u03c3<sub>\u03c9</sub>, in rad/ms.'
} );
// @public
this.inverseStandardDeviationProperty = new DynamicProperty( new Property( this.standardDeviationProperty ), {
bidirectional: true,
reentrant: true, // necessary because bidirectional:true
map: value => 1 / value, // standardDeviation => inverseStandardDeviation
inverseMap: value => 1 / value, // inverseStandardDeviation => standardDeviation
//TODO range: new Range( 1 / this.standardDeviationProperty.range.max, 1 / this.standardDeviationProperty.range.min,
//TODO phetioType: ???,
tandem: options.tandem.createTandem( 'this.inverseStandardDeviationProperty' ),
phetioDocumentation: 'Inverse standard deviation, a measure of the wave packet width. ' +
'In the space domain, this is \u03c3<sub>x</sub>, in m/rad. ' +
'In the time domain, this is \u03c3<sub>t</sub>, in ms/rad.'
} );
I am less experienced with DynamicProperty and not sure what's best here.
I'm currently using 2 NumberProperty instances, and handling my own synchronization.
That sounds advantageous, unless there are problems with that pattern?
unless there are problems with that pattern?
I'd probably call it more of a work around then a pattern. We have a pattern and library type set up to support this synchronization for us (DynamicProperty), but PhET-iO is not yet set up to support it.
Brainstorming here, I could see a way to support an instrumented DynamicProperty that is constrained in that its value Property never changes. If we can assert that, (that the intermediate Property never changes), then we can likely support DynamicProperty very easily, with a PropertyIO(ParameterTypeIO)
phetioType.
I got here by experimenting with a forwarding IOType, but then realized that the PhET-iO-ness of the DynamicProperty actually doesn't need to know anything about the other Property, because the map/inverseMap functions will be the same in the the upstream and downstream sims. Thus. I recommend this for the phetioType above: PropertyIO( NumberIO )
. For the range, it will get a bit messy, but that is only needed for studio slider control support, and not for getting things up and running as an mvp.
This patch is working in studio, but doesn't have a studio control just yet because of serializing with PropertyIO( NumberIO ) instead of NumberProperty.
Note this is not yet a complete instrumentation of DynamicProperty, but perhaps our first foot in the door for tackling it.
Side note: Here is an IOType I played with but feel like we don't need to support at this time:
@samreid, can you please comment on how close this pattern might be to production-ready. Main concerns:
range
at this time.@zepumph said:
unless there are problems with that pattern? I'd probably call it more of a work around then a pattern. We have a pattern and library type set up to support this synchronization for us (DynamicProperty), but PhET-iO is not yet set up to support it.
Yes, that was my point. Like I said, I have this working. But the fact that DynamicProperty doesn't have PhET-iO support seems like it needed addressing.
Note this is not yet a complete instrumentation of DynamicProperty, but perhaps our first foot in the door for tackling it.
This looks promising. But I had (have) many of the same questions as @samreid. So... For Fourier, I will likely stick with my workaround, unless a complete solution is available before August 24 (the "feature complete" milestone for Fourier). And reminder: Fourier 1.0 will not be published for PhET-iO brand.
unless a complete solution
By incomplete I was talking about DynamicProperty's ability to swap out the intermediate Property (something PhET-iO is not well set up for at all because of dynamicProperties). For the fourier usage, I believe that we could get something working very well.
Oh! I have a new idea @samreid. Why don't we create a DynamicPropertyIO type, and all it does is serialize is the exact thing my patch above does with one added piece. It can assert that the intermediateProperty is instrumented, and use referenceIO to serialize it. Let me write out an untested scrap for you.
I'm having trouble evaluating whether it is acceptable to require that instrumented DynamicProperty instances wrap Property instance which are also instrumented. Is that a natural fit, or are many of the wrapped Property instances implementation details which we would hope to leave uninstrumented? Even if so, would we be OK adding instrumentation to them, and making them be non-featured?
I think that seems like the easiest path forward, but it also feels like a total pain. I feel a bit like being graceful, and assuming that if you pass in an uninstrumented Property to an instrumented DynamicProperty, it is because that Property doesn't matter, and also doesn't change (therefore it doesn't need serialization). I don't think it is too clever, so long as we can explain how to support both cases.
It sounds reasonable to me, but wondering if we should touch base in a mini-meeting between myself and @jonathanolson and @zepumph. Perhaps as a breakout room at dev meeting?
I quite like that. Added it!
I'll mark as on hold, but only until @jonathanolson @zepumph and I can discuss, perhaps at an upcoming Thursday meeting. This helps filter it out of my "to do" list.
I ran into needing this to port something in Density/Buoyancy.
Defined initial types and type guards in NumberProperty: https://github.com/phetsims/axon/blob/7f0b33caffc1ebc5a19087096dfe4012b7f152f6/js/NumberProperty.ts#L44-L59
With asRanged() and friends that gives an initial check + cast in a safe-ish way: https://github.com/phetsims/axon/blob/7f0b33caffc1ebc5a19087096dfe4012b7f152f6/js/NumberProperty.ts#L313-L341
I added the long-needed MappedProperty (basically a DynamicProperty for a single Property, used for map/inverseMap), and a RangedMappedProperty that has a hard range (and implements RangedProperty).
Additionally for my needs, I implemented UnitConversionProperty, which is meant for converting units (number => number). It also converts the rangeProperty if needed (I was doing this manually before).
Thus a before (without type safety):
// For unit conversion
const volumeProperty = new DynamicProperty( new Property( model.volumeProperty ), {
map: cubicMeters => 1000 * cubicMeters,
inverseMap: liters => liters / 1000,
bidirectional: true
} );
volumeProperty.range = new Range( model.volumeProperty.range.min * 1000, model.volumeProperty.range.max * 1000 );
and after:
// For unit conversion, cubic meters => liters
const volumeProperty = new UnitConversionProperty( model.volumeProperty, {
factor: 1000
} ).asRanged();
where afterwards it has a guaranteed range to be used elsewhere (it types as RangedProperty
).
Also, UnitConverstionProperty can do non-linear things. Just provide map/inverseMap instead of factor (and it will still handle the range, typing, etc.)
This all sounds reasonable, and nice to know that these exist. Code looks clean, and it's nice that there are examples in the docs. I don't have time for a detailed test-drive right now, so may have some feedback in the future. Self unassigning.
@samreid I was assigned here. Is there anything I should do?
It's unclear to me whether we have addressed all of @pixelzoom requests, or if we will need something like DynamicPropertyIO as discussed in https://github.com/phetsims/axon/issues/358#issuecomment-891127785.
I definitely don't know what the desired behavior would be for a DynamicPropertyIO.
This issue is so old now that it's moot for Fourier. And my attempts to use DynamicProperty have generally not gone well, so I'll try my best to avoid it in the future. So I'm unassigning myself and unsubscribing from this issue. I'll leave it up to the PhET-iO team to decide whether it's worth the effort to figure this out. I don't care whether you close this issue, or continue to invesigate.
Discussed with @samreid, and he recommended that this issue be closed since we do not currently needs this functionality for DynamicProperty. Can be re-opened when that time comes.
I have a natural place to use a bidirectional
DynamicProperty
in Fourier, and I'm running into a couple of problems:(1) My DynamicProperty needs to have type 'number' and a Range, so that it can used with NumberControl. I see no way to define a range for DynamicProperty.
(2) My DynamicProperty needs to be PhET-iO instrumented and editable from Studio. I see no DynamicPropertyIO, so no way to specify
phetioType: DynamicPropertyIO( NumberIO )
.Any insights, examples, etc. would be appreciated.
@jonathanolson is the author of DynamicProperty, and seems to have used it the most. I'll start by assigning to him, and the PhET-iO team.