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())
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])
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)
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)
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)
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)