/* -*- tab-width: 4 -*-
*
* Electric(tm) VLSI Design System
*
* File: IndirectSet.java
* Written by Tom O'Neill, Sun Microsystems.
*
* Copyright (c) 2004 Sun Microsystems and Static Free Software
*
* Electric(tm) is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* Electric(tm) is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Electric(tm); see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, Mass 02111-1307, USA.
*/
package com.sun.electric.tool.simulation.test;
/**
* Adjusts voltage on a provided power supply channel until the specified
* current reaches the desired set point. The current and voltage must be
* correlated. Assumes the current is a monotonic function of voltage, and that
* the derivative is always nonzero.
* <p>
*
* We use a class instead of just a method so that caller can obtain resulting
* voltage and current without performing time-consuming readVoltage() and
* readCurrent() invocataions.
*/
public class IndirectSet {
/** Maximum number of convergence steps */
public static final int MAX_VOLTS_STEPS = 100;
/**
* Maximum fraction by which we ever expect to overshoot the target used to
* set range for <code>readCurrent()</code>.
*/
public static final float MAX_CURRENT_OVERSHOOT = 5.f;
/**
* Fraction of <code>ampsError</code> to set <code>readCurrent()</code>
* resolution to.
*/
public static final float EXCESS_RESOLUTION = 0.01f;
/** Best guess so far at voltage required to produce <code>setAmps</code> */
public float volts;
/** Current measured when voltage equals <code>volts</code> */
public float amps;
/**
* True if successfully set <code>amps</code> to approximately
* <code>setAmps</code>
*/
public boolean achievedCurrent = false;
// Desired current set point
private float setAmps;
// Current and voltage pairs bounding setAmps. Voltage should stay
// in range 0..maxVolts
private float ampsLow = -Float.MAX_VALUE, voltsLow = 0.f;
private float ampsHigh = Float.MAX_VALUE, voltsHigh;
// The current, voltage pairs closest to setAmps (warmer=closer).
// Initially assume 0 Volts produces 0 A, won't hurt if it isn't true.
private float ampsHot = 0.f, voltsHot = 0.f;
private float ampsWarm = -Float.MAX_VALUE, voltsWarm = 0.f;
// Derivative of voltage wrt current, and its value in previous iteration
private float voltsPerAmp, oldVoltsPerAmp = 0.f;
// Resolution of voltage setting
private float voltsResolution;
// Previous value of volts
private float oldVolts;
/**
* Adjust the voltage on <code>knob</code> until current on
* <code>dial</code> reaches the setpoint. A safe non-zero starting value
* <code>initVolts</code> must be provided.
* <p>
* Assumes <code>knob</code> provides the correct resolution for the power
* supply. If the current readback is noisier than <code>ampsError</code>,
* the routine may fail.
*
* @param dial
* object providing current readback
* @param setAmps
* desired current setpoint
* @param ampsError
* allowed deviation from setAmps
* @param knob
* object providing voltage control
* @param initVolts
* initial guess at correct voltage, must be non-zero
* @param maxVolts
* maximum allowed voltage
* @see PowerChannel#getVoltageResolution
*/
public IndirectSet(CurrentReadable dial, float setAmps, float ampsError,
PowerChannel knob, float initVolts, float maxVolts) {
this.setAmps = setAmps;
volts = initVolts;
voltsHigh = maxVolts;
// Safety limit on size of voltage change in a single iteration
float maxVoltsStep = Math.abs(0.2f * initVolts);
// Range and resolution for readCurrent()
float ampsMax = MAX_CURRENT_OVERSHOOT * setAmps;
float ampsResolution = EXCESS_RESOLUTION * ampsError;
voltsResolution = knob.getVoltageResolution();
for (int ind = 0; ind < 100; ind++) {
volts = Math.round(volts / voltsResolution) * voltsResolution;
knob.setVoltageWait(volts);
amps = dial.readCurrent(ampsMax, ampsResolution);
System.out.println(ind + ": " + volts + " V, " + amps + " A");
float ampsDiff = Math.abs(amps - setAmps);
//System.out.println(volts + " V, " + amps + " A");
if (ampsDiff <= ampsError) {
achievedCurrent = true;
return;
}
// Tighten bounds around setAmps if possible. If bounds within
// voltage resolution, choose better endpoint and return.
if (updateBounds(knob)) {
achievedCurrent = true;
return;
}
// Update record of current, voltage pairs to setAmps
updateClosePairs(setAmps, ampsDiff);
// Find slope of current-voltage relationship
if (!updateVoltsPerAmp(ind)) {
return;
}
// Compute new voltage assuming linear current-voltage relationship
computeNextVolts(maxVoltsStep);
// If new voltage outside bound around setAmps, then try the
// midpoint of the bounded region instead
if (volts < voltsLow || volts > voltsHigh) {
System.err.println("Warning: volts=" + volts
+ " outside bounds " + voltsLow + ".." + voltsHigh);
volts = 0.5f * (voltsLow + voltsHigh);
}
System.out.println(ind + ": " + getState() + "\n");
if (volts <= 0.f || volts > maxVolts) {
Infrastructure.nonfatal("Voltage not converging, reached "
+ volts + "V");
return;
}
}
Infrastructure.nonfatal("Voltage did not converge in "
+ IndirectSet.MAX_VOLTS_STEPS + " steps");
return;
}
/**
* Returns a string giving the complete state of the
* {@link IndirectSet} object.
*
* @return string giving the complete state of the object
*/
public String getState() {
String state = volts + " V, " + amps + " A; old=" + oldVolts + " V";
state += "\n low: " + voltsLow + " V, " + ampsLow + " A;";
state += " high: " + voltsHigh + "V, " + ampsHigh + " A;";
state += "\n hot: " + voltsHot + "V, " + ampsHot + " A;";
state += " warm: " + voltsWarm + "V, " + ampsWarm + " A;";
state += "\n voltsPerAmp: " + voltsPerAmp;
state += "V, oldVoltsPerAmp: " + oldVoltsPerAmp;
return state;
}
// Compute new voltage assuming linear current-voltage relationship.
// Ensures that the magnitued of the change is at most maxVoltsStep, and at
// least one voltage resolution unit
private void computeNextVolts(float maxVoltsStep) {
float voltsStep = (setAmps - amps) * voltsPerAmp;
if (Math.abs(voltsStep) > maxVoltsStep) {
if (voltsStep >= 0.f) {
voltsStep = maxVoltsStep;
} else {
voltsStep = -maxVoltsStep;
}
}
oldVolts = volts;
volts += voltsStep;
volts = Math.round(volts / voltsResolution) * voltsResolution;
if (volts == oldVolts) {
if (voltsStep > 0.f) {
volts += voltsResolution;
} else {
volts -= voltsResolution;
}
}
}
// Find slope of current-voltage relationship. Returns true if
// voltsPerAmp has same sign as previous reading.
private boolean updateVoltsPerAmp(int ind) {
boolean status = true;
voltsPerAmp = (voltsHot - voltsWarm) / (ampsHot - ampsWarm);
if (ind > 1 && (voltsPerAmp > 0.f && oldVoltsPerAmp < 0.f)
|| (voltsPerAmp < 0.f && oldVoltsPerAmp > 0.f)) {
Infrastructure.nonfatal("voltsPerAmp (dV/dI) =" + voltsPerAmp
+ ", was " + oldVoltsPerAmp);
status = false;
}
oldVoltsPerAmp = voltsPerAmp;
return status;
}
// Update record of current, voltage pairs to setAmps
private void updateClosePairs(float setAmps, float ampsDiff) {
/* Note actual data always overrides assumed value at (0 V, 0 A) */
if (ampsDiff < Math.abs(ampsHot - setAmps)
|| (ampsHot == 0.f && voltsHot == 0.f)) {
ampsWarm = ampsHot;
voltsWarm = voltsHot;
ampsHot = amps;
voltsHot = volts;
} else if (ampsDiff < Math.abs(ampsWarm - setAmps)) {
ampsWarm = amps;
voltsWarm = volts;
}
}
// Tighten bounds around setAmps if possible. If we have
// gotten voltage as close as it can be, choose closer bounds endpoint
// and return true. Otherwise return false.
private boolean updateBounds(PowerChannel knob) {
if (amps <= setAmps && amps > ampsLow) {
ampsLow = amps;
voltsLow = volts;
}
if (amps > setAmps && amps < ampsHigh) {
ampsHigh = amps;
voltsHigh = volts;
}
if (Math.abs(voltsHigh - voltsLow) <= (1.001f * voltsResolution)) {
if (Math.abs(ampsLow - setAmps) < Math.abs(ampsHigh - setAmps)) {
volts = voltsLow;
amps = ampsLow;
} else {
volts = voltsHigh;
amps = ampsHigh;
}
knob.setVoltageWait(volts);
return true;
}
return false;
}
public static void main(String[] args) {
}
}