예제 #1
0
    def test_generate_latfile_original2(self):
        # TEST LATFILE
        fout1_file = generate_latfile(self.m, latfile=self.latfile1)
        with open(fout1_file, 'rb') as f:
            f_str = f.read().strip().decode()
        self.assertEqual(f_str, self.fout1_str)

        with open(fout1_file, 'rb') as f:
            m = Machine(f)
        s = m.allocState({})
        r = m.propagate(s, 0, len(m), range(len(m)))

        # TEST RESULTS
        for i in range(len(m)):
            s1, s0 = r[i][1], self.r[i][1]
            for k in self.ref_keys:
                self.assertEqual(getattr(s1, k), getattr(s0, k))

            for k in self.keys:
                self.assertEqual(
                    getattr(s1, k).tolist(),
                    getattr(s0, k).tolist())

            for k in self.keys_ps:
                self.assertEqual(
                    getattr(s1, k).tolist(),
                    getattr(s0, k).tolist())
예제 #2
0
    def test_generate_latfile_update2(self):
        idx = 0
        s = self.m.allocState({})
        self.m.propagate(s, 0, 1)
        s.moment0 = [[0.1], [0.1], [0.1], [0.1], [0.1], [0.1], [1.0]]
        fout2_file = generate_latfile(self.m, latfile=self.latfile2, state=s)

        with open(fout2_file, 'rb') as f:
            m = Machine(f)
        assertAEqual(m.conf()['P0'], [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 1.0])
예제 #3
0
    def test_generate_latfile_update1(self):
        idx = 80
        self.m.reconfigure(idx, {'theta_x': 0.1})
        fout2_file = generate_latfile(self.m, latfile=self.latfile2)
        with open(fout2_file) as f:
            self.assertEqual(f.read().strip(), self.fout2_str)

        with open(fout2_file, 'rb') as f:
            m = Machine(f)
        self.assertEqual(m.conf(idx)['theta_x'], 0.1)
예제 #4
0
 def test_generate_latfile_original1(self):
     sio = StringIO()
     sioname = generate_latfile(self.m, out=sio)
     self.assertEqual(sioname, 'string')
     self.assertEqual(sio.getvalue().strip(), self.fout1_str)
예제 #5
0
 def test_generate_latfile_original4(self):
     sio = StringIO()
     sioname = generate_latfile(self.m, start=30, end=60, out=sio)
     self.assertEqual(sioname, 'string')
     self.assertEqual(sio.getvalue().strip(), self.fout4_str)
예제 #6
0
    def _co_execute(self):
        """Execute the virtual accelerator. This includes the following:

        1. Creating a temporary working directory for execution of FLAME.
        2. Set up the working directory by symlinking from the data directory.
        3. Writing the EPICS DB to the working directory (va.db).
        4. Starting the softIoc and channel initializing monitors.
        5. Add noise to the settings for all input (CSET) channels.
        6. Create or update the FLAME machine configuration.
        7. Propagate the FLAME simulation and read the results.
        8. Update the READ channels of all devives.
        9. Update the REST channels of input devies.
        10. Repeat from step #5.
        """
        _LOGGER.debug("VA: Execute virtual accelerator")
        _LOGGER.info("VA: Running at " + self._ts_now)

        chanprefix = self._chanprefix
        if self._pv_suffix != '':
            suffix_str = "_" + self._pv_suffix
        else:
            suffix_str = ""

        # Add channel for VA rep-rate
        chanrate = f"{chanprefix}:SVR:RATE{suffix_str}"
        self._epicsdb.append(("ao", chanrate, OrderedDict([
            ("DESC", "Rep-rate of Simulation Engine"),
            ("VAL", self._rate),
            ("PREC", 1),
            ])))
        _LOGGER.info("VA: Reprate PV is " + chanrate)


        # Add channel for sample counting
        chansample_cnt = f"{chanprefix}:SVR:CNT{suffix_str}"
        self._epicsdb.append(("ao", chansample_cnt, OrderedDict([
            ("DESC", "Sample counter for scan client"),
            ("VAL", 0)
            ])))

        # Add channel for VA configuration and control
        channoise = f"{chanprefix}:SVR:NOISE{suffix_str}"
        self._epicsdb.append(("ao", channoise, OrderedDict([
                ("DESC", "Noise level of Virtual Accelerator"),
                ("VAL", self._noise),
                ("PREC", 5)
            ])))
        _LOGGER.info("VA: Noise PV is " + channoise)

        chanstat = f"{chanprefix}:SVR:STATUS{suffix_str}"
        self._epicsdb.append(("bi", chanstat, OrderedDict([
                ("DESC", "Status of Virtual Accelerator"),
                ("VAL", 1),
                ("ZNAM", "ERR"),
                ("ONAM", "OK"),
                ("PINI", "1")
            ])))
        _LOGGER.info("VA: Status PV is " + chanstat)

        # MPS status
        chan_mps_stat = f"{chanprefix}:SVR:MpsStatus"
        self._epicsdb.append(("mbbi", chan_mps_stat, OrderedDict([
                ("DESC", "MPS Status of Virtual Accelerator"),
                ("VAL", 3),
                ("ZRST", "Fault"),
                ("ONST", "Disable"),
                ("TWST", "Monitor"),
                ("THST", "Enable"),
                ("PINI", "1")
            ])))
        _LOGGER.info("VA: MPS PV is " + chan_mps_stat)

        #
        chancharge = f"{chanprefix}:SVR:CHARGE{suffix_str}"
        self._epicsdb.append(("ai", chancharge, OrderedDict([
                ("DESC", "Q/M of Virtual Accelerator"),
                ("VAL", 0.0),
                ("PREC", 5)
            ])))

        # initial beam condition
        chanbsrc = f"{chanprefix}:SVR:BEAM{suffix_str}"
        self._epicsdb.append(("waveform", chanbsrc, OrderedDict([
                ("DESC", "Init beam of Virtual Accelerator"),
                ("NELM", 4096),
                ("FTVL", "UCHAR")
            ])))
        _LOGGER.info("VA: Init beam condition PV is " + chanbsrc)

        #
        if self.work_dir is not None:
            os.makedirs(self.work_dir)
            self._rm_work_dir = False
            latticepath = os.path.join(self.work_dir, "test.lat")
        else:
            self.work_dir = tempfile.mkdtemp(_TEMP_DIRECTORY_SUFFIX)
            self._rm_work_dir = True
            latticepath = None

        _LOGGER.info("VA: Working directory: %s", self._work_dir)

        # input file paths
        epicsdbpath = os.path.join(self.work_dir, "va.db")

        #output file paths
        epicslogpath = os.path.join(self.work_dir, "softioc.log")

        if os.path.isabs(self.data_dir):
            abs_data_dir = self.data_dir
            self._latfactory.dataDir = self.data_dir
        else:
            abs_data_dir = os.path.abspath(self.data_dir)
            self._latfactory.dataDir = os.path.abspath(self.data_dir)

        with open(epicsdbpath, "w") as outfile:
            self._write_epicsdb(outfile)
        _LOGGER.info("VA: Write EPICS database to %s", epicsdbpath)
        #_LOGGER.debug("VA: Write EPICS database to %s", epicsdbpath)

        self._ioc_logfile = open(epicslogpath, "w")
        self._ioc_process = Popen(["softIoc", "-d", "va.db"], cwd=self.work_dir,
                                  stdout=self._ioc_logfile, stderr=subprocess.STDOUT)
        _LOGGER.debug("VA: Start EPICS soft IOC with log %s", epicslogpath)

        _LOGGER.debug("VA: Connecting to channels: {}".format(len(self._csetmap.keys())))

        self._subscriptions = []
        self._subscriptions.append(catools.camonitor(chanrate, self._handle_rate_monitor))
        self._subscriptions.append(catools.camonitor(channoise, self._handle_noise_monitor))
        self._subscriptions.append(catools.camonitor(chanbsrc, self._handle_bsrc_monitor))
        self._subscriptions.extend(catools.camonitor(self._csetmap.keys(), self._handle_cset_monitor))
        _LOGGER.debug("VA: Connecting to channels: Done")

        machine = None

        while self._continue:
            # update the RSET channels with new settings
            batch = catools.CABatch()
            for cset in self._csetmap.items():
                name, field = self._fieldmap[cset[0]]
                batch[cset[1][0]] = self._settings[name][field]
            batch.caput()

            settings = self._copy_settings_with_noise()
            self._latfactory.settings = settings
            lattice = self._latfactory.build()

            start = time.time()

            if machine is None:
                _LOGGER.debug("VA: Create FLAME machine from configuration")
                machine = Machine(lattice.conf())
            else:
                _LOGGER.debug("VA: Reconfigure FLAME machine from configuration")
                for idx, elem in enumerate(lattice.elements):
                    machine.reconfigure(idx, elem[2])

                if self._bsrc is not None:
                    _LOGGER.info("VA: Reconfigure FLAME machine with init beam config")
                    machine.reconfigure(self._bsrc['index'], self._bsrc['properties'])

            if latticepath is not None:
                _LOGGER.debug(f"VA: Write FLAME lattice file to {outfile}")
                generate_latfile(machine, latfile=latticepath)

            _LOGGER.debug("VA: Allocate FLAME state from configuration")
            S = machine.allocState({})

            output_map = []
            for elem in lattice.elements:
                if 'name' in elem[3]:
                    output_map.append(elem[3]['name'])
                else:
                    output_map.append(None)

            batch = catools.CABatch()
            for i in range(0, len(machine)):
                machine.propagate(S, i, 1)

                if output_map[i] in self._elemmap:
                    elem = self._elemmap[output_map[i]]

                    if isinstance(elem, BPMElement):
                        x_centroid = S.moment0_env[0]/1.0e3 # convert mm to m
                        _LOGGER.debug("VA: Update read: %s to %s",
                                      self._readfieldmap[elem.name][elem.fields.x_phy], x_centroid)
                        batch[self._readfieldmap[elem.name][elem.fields.x_phy]] = x_centroid
                        y_centroid = S.moment0_env[2]/1.0e3 # convert mm to m
                        _LOGGER.debug("VA: Update read: %s to %s",
                                      self._readfieldmap[elem.name][elem.fields.y_phy], y_centroid)
                        batch[self._readfieldmap[elem.name][elem.fields.y_phy]] = y_centroid
                         # convert rad to deg and adjust for 161MHz sampling frequency
                        phase = _normalize_phase(2.0 * S.ref_phis * (180.0 / math.pi))
                        _LOGGER.debug("VA: Update read: %s to %s",
                                      self._readfieldmap[elem.name][elem.fields.phase_phy], phase)
                        batch[self._readfieldmap[elem.name][elem.fields.phase_phy]] = phase
                        energy = S.ref_IonEk/1.0e6 # convert eV to MeV
                        _LOGGER.debug("VA: Update read: %s to %s",
                                      self._readfieldmap[elem.name][elem.fields.energy_phy], energy)
                        batch[self._readfieldmap[elem.name][elem.fields.energy_phy]] = energy

                    elif isinstance(elem, PMElement):
                        x_centroid = S.moment0_env[0]/1.0e3 # convert mm to m
                        _LOGGER.debug("VA: Update read: %s to %s",
                                      self._readfieldmap[elem.name][elem.fields.x], x_centroid)
                        batch[self._readfieldmap[elem.name][elem.fields.x]] = x_centroid
                        y_centroid = S.moment0_env[2]/1.0e3 # convert mm to m
                        _LOGGER.debug("VA: Update read: %s to %s",
                                      self._readfieldmap[elem.name][elem.fields.y], y_centroid)
                        batch[self._readfieldmap[elem.name][elem.fields.y]] = y_centroid
                        x_rms = S.moment0_rms[0]/1.0e3 # convert mm to m
                        _LOGGER.debug("VA: Update read: %s to %s",
                                      self._readfieldmap[elem.name][elem.fields.xrms], x_rms)
                        batch[self._readfieldmap[elem.name][elem.fields.xrms]] = x_rms
                        y_rms = S.moment0_rms[2]/1.0e3
                        _LOGGER.debug("VA: Update read: %s to %s",
                                      self._readfieldmap[elem.name][elem.fields.yrms], y_rms)
                        batch[self._readfieldmap[elem.name][elem.fields.yrms]] = y_rms

                        sign = elem.sign
                        xy_centroid = (sign*x_centroid + y_centroid)/math.sqrt(2.0) # convert mm to m
                        _LOGGER.debug("VA: Update read: %s to %s",
                                      self._readfieldmap[elem.name][elem.fields.xy], xy_centroid)
                        batch[self._readfieldmap[elem.name][elem.fields.xy]] = xy_centroid

                        xy_rms = 1.0e-3*math.sqrt(
                                (S.moment1_env[0, 0] + S.moment1_env[2, 2])*0.5
                                + sign*S.moment1_env[0, 2]
                        )
                        _LOGGER.debug("VA: Update read: %s to %s",
                                      self._readfieldmap[elem.name][elem.fields.xyrms], xy_rms)
                        batch[self._readfieldmap[elem.name][elem.fields.xyrms]] = xy_rms

                        cxy = sign * S.moment1_env[0, 2] * 1e-6 / x_rms / y_rms
                        _LOGGER.debug("VA: Update read: %s to %s",
                                      self._readfieldmap[elem.name][elem.fields.cxy], cxy)
                        batch[self._readfieldmap[elem.name][elem.fields.cxy]] = cxy

                    elif isinstance(elem, (FCElement, VDElement, TargetElement, DumpElement, WedgeElement)):
                        x_centroid = S.moment0_env[0]/1.0e3 # convert mm to m
                        _LOGGER.debug("VA: Update read: %s to %s",
                                      self._readfieldmap[elem.name][elem.fields.x], x_centroid)
                        batch[self._readfieldmap[elem.name][elem.fields.x]] = x_centroid
                        y_centroid = S.moment0_env[2]/1.0e3 # convert mm to m
                        _LOGGER.debug("VA: Update read: %s to %s",
                                      self._readfieldmap[elem.name][elem.fields.y], y_centroid)
                        batch[self._readfieldmap[elem.name][elem.fields.y]] = y_centroid
                        x_rms = S.moment0_rms[0]/1.0e3 # convert mm to m
                        _LOGGER.debug("VA: Update read: %s to %s",
                                      self._readfieldmap[elem.name][elem.fields.xrms], x_rms)
                        batch[self._readfieldmap[elem.name][elem.fields.xrms]] = x_rms
                        y_rms = S.moment0_rms[2]/1.0e3
                        _LOGGER.debug("VA: Update read: %s to %s",
                                      self._readfieldmap[elem.name][elem.fields.yrms], y_rms)
                        batch[self._readfieldmap[elem.name][elem.fields.yrms]] = y_rms

            batch.caput()

            _LOGGER.info("VA: FLAME execution time: %f s", time.time()-start)

            # Allow the BPM, PM, etc. readbacks to update
            # before the device setting readbacks PVs.
            cothread.Yield()

            batch = catools.CABatch()
            for name, value in self._csetmap.items():
                name, field = self._fieldmap[name]
                _LOGGER.debug("VA: Update read: %s to %s", value[1], settings[name][field])
                batch[value[1]] = settings[name][field]
            batch.caput()

            # Sleep for a fraction (10%) of the total execution time
            # when one simulation costs more than 0.50 seconds.
            # Otherwise, sleep for the rest of 1 second.
            # If a scan is being done on this virtual accelerator,
            # then the scan server has a period of time to update
            # setpoints before the next run of IMPACT.
            delt = time.time() - start
            if delt > 0.50:
                cothread.Sleep(delt * 0.1)
            else:
                cothread.Sleep(1.0 / self._rate - delt)