예제 #1
0
 def __heatLink(self, coolertemp, sampletemp):
     """heatflow from sample to cooler. may be negative..."""
     flow = (sampletemp - coolertemp) * \
            ((coolertemp + sampletemp) ** 2)/400.
     cp = clamp(self.__coolerCP(coolertemp) * self.__sampleCP(sampletemp),
                1, 10)
     return clamp(flow, -cp, cp)
예제 #2
0
def calculate(lam, spread, reso, shade, l, dtau, n_max):
    """Calculate chopper frequency and opening angle for a single setting,
    which needs:

    * lam: incoming neutron wavelength in Angstrom
    * spread: incoming neutron wavelength spread from selector
    * reso: desired wavelength resolution
    * shade: shade of spectrum edges
    * l: chopper-detector length in m
    * dtau: additional offset of TOF in ms
    * n_max: maximum number of acquisition frames

    Returns: (frequency in Hz, opening angle in deg)
    """

    dlam = 2.0 * spread * lam
    tau_tot = l * lam * (1.0 - spread) / SPEED

    n = max(2, int(2 * spread / reso + 1))
    n_fac = (1. - 2 * shade) / (1. - shade)
    n_fac = clamp(n_fac, 1.0 / n, 1.0)
    n_illum = clamp(int(n_fac * n), 1, n_max)

    tau_delta = l * dlam / ((n - 1) * SPEED)
    tau_win = 0.5 * tau_delta * (n + n_illum)
    tau0 = tau_tot + 0.5 * (n - n_illum) * tau_delta + dtau
    tau0 -= tau_win * int(tau0 / tau_win)

    freq = 1000.0 / tau_win / 2.0
    angle = 180.0 / (0.5 * (n + n_illum))

    return freq, angle
예제 #3
0
 def doWriteLimits(self, limits):
     lower, upper = limits
     upper = clamp(upper, 1e-3, self.parallel_pumping)
     lower = clamp(lower, 1e-3, upper)
     self._attached_iodev._HW_rawCommand('set_%s_low' % self.chamber,
                                         int(lower * 1e6))
     self._attached_iodev._HW_rawCommand('set_%s_high' % self.chamber,
                                         int(upper * 1e6))
     return (lower, upper)
예제 #4
0
 def doReadAbslimits(self):
     maxcurr = self._attached_currentsource.abslimits[1]
     maxfield = self._current2field(maxcurr)
     # get configured limits if any, or take max of source
     limits = self._config.get('abslimits', (-maxfield, maxfield))
     # in any way, clamp limits to what the source can handle
     limits = [clamp(i, -maxfield, maxfield) for i in limits]
     return min(limits), max(limits)
예제 #5
0
 def doWriteRamp(self, value):
     # this works only for the floatrange type of the ramp parameter!
     rampmin = self.parameters['ramp'].type.fr
     rampmax = self.parameters['ramp'].type.to
     if value == 0.0:
         value = rampmax
         self.log.warning(
             'Ramp rate of 0 is deprecated, using %d '
             'K/min instead', value)
     self.__set_param('ramp', clamp(value, rampmin, rampmax))
     return self.__get_param('ramp')
예제 #6
0
 def doReadAbslimits(self):
     minfield, maxfield = [
         self._current2field(I)
         for I in self._attached_currentsource.abslimits
     ]
     # include 0 in allowed range
     if minfield > 0:
         minfield = 0
     if maxfield < 0:
         maxfield = 0
     # get configured limits if any, or take max from source
     limits = self._config.get('abslimits', (minfield, maxfield))
     # in any way, clamp limits to what the source can handle
     limits = [clamp(i, minfield, maxfield) for i in limits]
     return min(limits), max(limits)
예제 #7
0
 def doReadRamp(self):
     # do not return a value the validator would reject, or
     # device creation fails
     ramp = self.__get_param('ramp')
     # this works only for the floatrange type of the ramp parameter!
     rampmin = self.parameters['ramp'].type.fr
     rampmax = self.parameters['ramp'].type.to
     if rampmin <= ramp <= rampmax:
         return ramp
     clampramp = clamp(ramp, rampmin, rampmax)
     self.log.warning(
         'Ramp parameter %.3g is outside of the allowed range '
         '%.3g..%.3g, setting it to %.3g', ramp, rampmin, rampmax,
         clampramp)
     # clamp read value to allowed range and re-set it
     return self.doWriteRamp(clampramp)
예제 #8
0
 def __init__(self, dev, target):
     # limit the position to allowed values
     target = clamp(target, dev.usermin, dev.usermax)
     SeqDev.__init__(self, dev, target)
예제 #9
0
    def __moving(self):
        # complex thread handling:
        # a) simulation of cryo (heat flow, thermal masses,....)
        # b) optional PID temperature controller with windup control
        # c) generating status+updated value+ramp
        # this thread is not supposed to exit!

        # local state keeping:
        regulation = self.regulation
        sample = self.sample
        timestamp = time.time()
        heater = 0
        lastflow = 0
        last_heaters = (0, 0)
        delta = 0
        I = D = 0
        lastD = 0
        damper = 1
        lastmode = self.mode
        while not self._stopflag:
            t = time.time()
            h = t - timestamp
            if h < self.loopdelay / damper:
                time.sleep(clamp(self.loopdelay / damper - h, 0.1, 60))
                continue
            h *= self.speedup
            # a)
            sample = self.sample
            regulation = self.regulation
            heater = self.heater

            heatflow = self.__heatLink(regulation, sample)
            self.log.debug('sample = %.5f, regulation = %.5f, heatflow = %.5g',
                           sample, regulation, heatflow)
            newsample = max(0,
                            sample + (self.__sampleLeak(sample) - heatflow) /
                            self.__sampleCP(sample) * h)
            # avoid instabilities due to too small CP
            newsample = clamp(newsample, sample, regulation)
            regdelta = (heater * 0.01 * self.maxpower + heatflow -
                        self.__coolerPower(regulation))
            newregulation = max(0, regulation +
                                regdelta / self.__coolerCP(regulation) * h)

            # b) see
            # http://brettbeauregard.com/blog/2011/04/improving-the-beginners-pid-introduction/
            if self.mode != 'openloop':
                # fix artefacts due to too big timesteps
                # actually i would prefer reducing loopdelay, but i have no
                # good idea on when to increase it back again
                if heatflow * lastflow != -100:
                    if (newregulation - newsample) * (regulation - sample) < 0:
                        # newregulation = (newregulation + regulation) / 2
                        # newsample = (newsample + sample) / 2
                        damper += 1
                lastflow = heatflow

                error = self.setpoint - newregulation
                # use a simple filter to smooth delta a little
                delta = (delta + regulation - newregulation) / 2.

                kp = self.p / 10.             # LakeShore P = 10*k_p
                ki = kp * abs(self.i) / 500.  # LakeShore I = 500/T_i
                kd = kp * abs(self.d) / 2.    # LakeShore D = 2*T_d

                P = kp * error
                I += ki * error * h
                D = kd * delta / h

                # avoid reset windup
                I = clamp(I, 0., 100.)  # I is in %

                # avoid jumping heaterpower if switching back to pid mode
                if lastmode != self.mode:
                    # adjust some values upon switching back on
                    I = self.heater - P - D

                v = P + I + D
                # in damping mode, use a weighted sum of old + new heaterpower
                if damper > 1:
                    v = ((damper ** 2 - 1) * self.heater + v) / damper ** 2

                # damp oscillations due to D switching signs
                if D * lastD < -0.2:
                    v = (v + heater) / 2.
                # clamp new heater power to 0..100%
                heater = clamp(v, 0., 100.)
                lastD = D

                self.log.debug('PID: P = %.2f, I = %.2f, D = %.2f, '
                               'heater = %.2f', P, I, D, heater)

                # check for turn-around points to detect oscillations ->
                # increase damper
                x, y = last_heaters
                if (x + 0.1 < y and y > heater + 0.1) or \
                   (x > y + 0.1 and y + 0.1 < heater):
                    damper += 1
                last_heaters = (y, heater)

            else:
                # self.heaterpower is set manually, not by pid
                heater = self.heater
                last_heaters = (0, 0)

            heater = round(heater, 3)
            sample = newsample
            regulation = newregulation
            lastmode = self.mode

            # c)
            if self.setpoint != self.target:
                if self.ramp == 0:
                    maxdelta = 10000
                else:
                    maxdelta = self.ramp / 60. * h
                try:
                    self.setpoint = round(self.setpoint +
                                          clamp(self.target - self.setpoint,
                                                -maxdelta, maxdelta), 3)
                    self.log.debug('setpoint changes to %r (target %r)',
                                   self.setpoint, self.target)
                except (TypeError, ValueError):
                    # self.target might be None
                    pass

            # keep max self.window seconds long history
            self._cacheCB('value', regulation, t)

            # temperature is stable when all recorded values in the window
            # differ from setpoint by less than tolerance
            with self._statusLock:
                if self.setpoint == self.target:
                    self._setROParam('curstatus', (status.OK, ''))
                    damper -= (damper - 1) / 10.  # max value for damper is 11
                else:
                    self._setROParam('curstatus',
                                     (status.BUSY, 'ramping setpoint'))
            damper -= (damper - 1) / 20.
            self._setROParam('regulation', round(regulation, 3))
            self._setROParam('sample', round(sample, 3))
            self._setROParam('heaterpower',
                             round(heater * self.maxpower * 0.01, 3))
            self._setROParam('heater', heater)
            timestamp = t
예제 #10
0
 def __coolerPower(self, temp):
     """returns cooling power in W at given temperature"""
     # quadratic up to 42K, is linear from 40W@42K to 100W@600K
     # return clamp((temp-2)**2 / 32., 0., 40.) + temp * 0.1
     return clamp(15 * atan(temp * 0.01) ** 3, 0., 40.) + temp * 0.1 - 0.2
예제 #11
0
 def doWriteMaxpower(self, newpower):
     self.heater = clamp(self.heater * self.maxpower / float(newpower),
                         0, 100)