def _parse_stimulus_values(self, NAME, stimulus_values, variables, to_be_continued, is_appending): if not self.val[kw.STIMULUS_ELEMENTS]: return f"The parameter 'stimulus_elements' must be assigned before the parameter '{NAME}'." # Create and populate the struct with None values if not is_appending: self.val[NAME] = dict() for e in self.val[kw.STIMULUS_ELEMENTS]: self.val[NAME][e] = None self.val[NAME][kw.DEFAULT] = None single_w, _ = ParseUtil.evaluate(stimulus_values, variables) if single_w is not None: if is_appending: return "A single value for '{}' cannot follow other values.".format( NAME) elif to_be_continued: return "A single value for '{}' cannot be followed by other values.".format( NAME) else: for key in self.val[NAME]: self.val[NAME][key] = single_w self.val[NAME].pop(kw.DEFAULT) else: ws = ParseUtil.comma_split(stimulus_values) ws = [x.strip() for x in ws] for e_w_str in ws: # eb_w_str is 'e:value' or 'default:value' if e_w_str.count(':') != 1: return "Expected 'element:value' or 'default:value' in '{}', got '{}'.".format( NAME, e_w_str) e, w_str = e_w_str.split(':') e = e.strip() w_str = w_str.strip() w, err = ParseUtil.evaluate(w_str, variables) if err: return "Invalid value '{}' for '{}' in parameter '{}'.".format( w_str, e, NAME) if e == kw.DEFAULT: if self.val[NAME][kw.DEFAULT] is not None: return "Default value for '{}' can only be stated once.".format( NAME) elif e not in self.val[kw.STIMULUS_ELEMENTS]: return f"Error in parameter '{NAME}': '{e}' is an invalid stimulus element." if self.val[NAME][e] is not None: return "Duplicate of {} in '{}'.".format(e, NAME) self.val[NAME][e] = w if not to_be_continued: # Set the default value for non-set stimulus elements err = self._set_default_values(NAME) if err: return err return None # No error
def _parse_behavior_cost(self, behavior_cost_str, variables, to_be_continued, is_appending): if not self.val[kw.BEHAVIORS]: return f"The parameter 'behaviors' must be assigned before the parameter '{kw.BEHAVIOR_COST}'." # Create and populate the struct with None values if not is_appending: self.val[kw.BEHAVIOR_COST] = dict() for e in self.val[kw.BEHAVIORS]: self.val[kw.BEHAVIOR_COST][e] = None self.val[kw.BEHAVIOR_COST][kw.DEFAULT] = None single_c, _ = ParseUtil.evaluate(behavior_cost_str, variables) if single_c is not None: if is_appending: return "A single value for '{}' cannot follow other values.".format( kw.BEHAVIOR_COST) elif to_be_continued: return "A single value for '{}' cannot be followed by other values.".format( kw.BEHAVIOR_COST) else: for key in self.val[kw.BEHAVIOR_COST]: self.val[kw.BEHAVIOR_COST][key] = single_c self.val[kw.BEHAVIOR_COST].pop(kw.DEFAULT) else: cs = ParseUtil.comma_split(behavior_cost_str) cs = [x.strip() for x in cs] for bc_str in cs: # bc_str is 'e:value' or 'default:value' if bc_str.count(':') != 1: return "Expected 'element:value' or 'default:value' in '{}', got '{}'.".format( kw.BEHAVIOR_COST, bc_str) b, c_str = bc_str.split(':') b = b.strip() c_str = c_str.strip() c, err = ParseUtil.evaluate(c_str, variables) if err: return f"Invalid value '{c_str}' for '{b}' in parameter '{kw.BEHAVIOR_COST}'." if b == kw.DEFAULT: if self.val[kw.BEHAVIOR_COST][kw.DEFAULT] is not None: return "Default value for '{}' can only be stated once.".format( kw.BEHAVIOR_COST) elif b not in self.val[kw.BEHAVIORS]: return f"Error in parameter '{kw.BEHAVIOR_COST}': '{b}' is an invalid behavior name." if self.val[kw.BEHAVIOR_COST][b] is not None: return "Duplicate of {} in '{}'.".format( b, kw.BEHAVIOR_COST) self.val[kw.BEHAVIOR_COST][b] = c if not to_be_continued: # Set the default value for non-set behaviors err = self._set_default_values(kw.BEHAVIOR_COST) if err: return err return None # No error
def is_met(self, variables, event_counter): ismet, err = ParseUtil.evaluate(self.cond, variables, event_counter, ParseUtil.STOP_COND) if err: raise ParseException(self.lineno, err) if type(ismet) is not bool: raise ParseException(self.lineno, f"Condition '{self.cond}' is not a boolean expression.") return ismet
def _goto_if_met(self, variables): goto_prob_cumsum = list() cumsum = 0 for i in range(len(self.goto)): prob = self.goto[i][0] if type(prob) is str: self.goto[i][0], err = ParseUtil.evaluate(prob, variables=variables) if err: raise ParseException(self.lineno, err) cumsum += self.goto[i][0] goto_prob_cumsum.append(cumsum) if cumsum > 1: raise ParseException(self.lineno, f"Sum of probabilities is {cumsum}>1.") ind = ParseUtil.weighted_choice(goto_prob_cumsum) if ind is None: return None else: return self.goto[ind][1]
def is_met(self, response, global_variables, local_variables, event_counter): variables_both = Variables.join(global_variables, local_variables) if self.condition is None: ismet = True elif self.condition_is_behavior: ismet = (self.condition == response) else: ismet, err = ParseUtil.evaluate(self.condition, variables_both, event_counter, ParseUtil.PHASE_LINE) if err: raise ParseException(self.lineno, err) if type(ismet) is not bool: raise ParseException(self.lineno, f"Condition '{self.condition}' is not a boolean expression.") if ismet: label = self._goto_if_met(variables_both) if label is None: # In "ROW1(0.1),ROW2(0.3)", goto_if_met returns None with prob. 0.6 ismet = False else: label = None return ismet, label
def _perform_action(self, lineno, action): """ Sets a variable (x:3) or count_reset(event). """ omit_learn = False # No action to perform if len(action) == 0: pass # Setting a variable elif action.count(':') == 1 or action.count('=') == 1: if action.count('=') == 1: sep = '=' else: sep = ':' var_name, value_str = ParseUtil.split1_strip(action, sep=sep) variables_join = Variables.join(self.global_variables, self.local_variables) value, err = ParseUtil.evaluate(value_str, variables_join) if err: raise ParseException(lineno, err) err = self.local_variables.set(var_name, value, self.parameters) if err: raise ParseException(lineno, err) # count_reset elif action.startswith("count_reset(") and action.endswith(")"): event = action[12:-1] self.event_counter.reset_count(event) # omit_learn elif action == "@omit_learn": omit_learn = True else: raise ParseException(lineno, "Internal error.") # Should have been caught during Pase.parse() return omit_learn
def str_set(self, prop, v_str, variables, phases, all_run_labels, to_be_continued, is_appending=False): """ Parses the specified value (as a string) of the specified parameter and sets the resulting value. The input variables is a Variables object. Returns error message if parsing failed. """ err = check_is_parameter_name(prop) if err: return err # all_phase_labels = phases.labels_set() if prop == kw.BEHAVIORS: return self._parse_behaviors(v_str, variables, is_appending) elif prop == kw.STIMULUS_ELEMENTS: return self._parse_stimulus_elements(v_str, variables, is_appending) elif prop == kw.MECHANISM_NAME: return self._parse_mechanism_name(v_str) elif prop in (kw.START_VSS, kw.ALPHA_VSS): return self._parse_alphastart_vss(prop, v_str, variables, to_be_continued, is_appending) elif prop in (kw.START_W, kw.ALPHA_W, kw.U, kw.LAMBDA): return self._parse_stimulus_values(prop, v_str, variables, to_be_continued, is_appending) elif prop in (kw.BETA, kw.MU, kw.START_V, kw.ALPHA_V): return self._parse_stimulus_response_values( prop, v_str, variables, to_be_continued, is_appending) # Float elif prop in (kw.DISCOUNT, kw.TRACE): v, err = ParseUtil.evaluate(v_str, variables) if err: return err if (v < 0) or (v > 1): return f"Parameter '{prop}' must be a number >=0 and <=1." self.val[prop] = v return None elif prop == kw.BEHAVIOR_COST: return self._parse_behavior_cost(v_str, variables, to_be_continued, is_appending) elif prop == kw.RESPONSE_REQUIREMENTS: return self._parse_response_requirements(v_str, to_be_continued, is_appending) # 'on' or 'off' elif prop in (kw.BIND_TRIALS, kw.CUMULATIVE): v_str_lower = v_str.lower() if v_str_lower not in ('on', 'off'): return "Parameter '{}' must be 'on' or 'off'.".format(prop) self.val[prop] = v_str_lower return None # Positive integer elif prop == kw.N_SUBJECTS: v, err = ParseUtil.parse_posint(v_str, variables) if err: return err if not v: return "Parameter {} must be a positive integer.".format( kw.N_SUBJECTS) self.val[kw.N_SUBJECTS] = v return None # Any nonempty (after strip) string elif prop in (kw.TITLE, kw.SUBPLOTTITLE): if to_be_continued: # Add the removed comma v_str = v_str + "," self.val[prop] = v_str return None # 'average', 'all' or 1-based index elif prop == kw.SUBJECT: return self._parse_subject(v_str, variables) # 'all' or s1->b1->s2->..., s=se1,se2,... elif prop == kw.XSCALE: return self._parse_xscale(v_str, phases) # 'subset' or 'exact' elif prop in (kw.MATCH, kw.XSCALE_MATCH): if v_str.lower() not in ('subset', 'exact'): return "Parameter {} must be 'subset' or 'exact'.".format(prop) self.val[prop] = v_str return None # 'all' or cs-list of phase labels elif prop == kw.PHASES: return self._parse_phases(v_str) # , all_phase_labels) # String (@run-labels) (for postprocessing) elif prop == kw.RUNLABEL: if v_str not in all_run_labels: return "Invalid @RUN-label {}".format(v_str) self.val[kw.RUNLABEL] = v_str return None # Valid path to writable file elif prop == kw.FILENAME: filename = v_str try: file = open(filename, 'w', newline='') except Exception as ex: return str(ex) finally: file.close() try: os.remove(filename) except FileNotFoundError: pass self.val[kw.FILENAME] = filename return None
def _parse_alphastart_vss(self, NAME, vss_str, variables, to_be_continued, is_appending): """ Parse the string vss_str with a start_vss/alpha_vss specification. Example: "S1->S2: 1.23, S2->S1:3.45, default:1" sets the parameter to {('S1','S2'):1.23, ('S2','S1'):3.45, ('S1','S1'):1, ('S2','S2'):1} under the assumption that stimulus_elements = {'S1', 'S2'} """ if not self.val[kw.STIMULUS_ELEMENTS]: return f"The parameter 'stimulus_elements' must be assigned before the parameter '{NAME}'." # Create and populate the struct with None values if not is_appending: self.val[NAME] = dict() for e1 in self.val[kw.STIMULUS_ELEMENTS]: for e2 in self.val[kw.STIMULUS_ELEMENTS]: self.val[NAME][(e1, e2)] = None self.val[NAME][kw.DEFAULT] = None single_vss, _ = ParseUtil.evaluate(vss_str, variables) if single_vss is not None: if is_appending: return f"A single value for '{NAME}' cannot follow other values." elif to_be_continued: return f"A single value for '{NAME}' cannot be followed by other values." else: for key in self.val[NAME]: self.val[NAME][key] = single_vss self.val[NAME].pop(kw.DEFAULT) else: vs = ParseUtil.comma_split(vss_str) vs = [x.strip() for x in vs] for ee_str in vs: # eb_v_str is 'e1->e2:value' or 'default:value' if ee_str.count(':') != 1: return f"Expected 'x->y:value' or 'default:value' in '{NAME}', got '{ee_str}'." ee, v_str = ee_str.split(':') ee = ee.strip() v_str = v_str.strip() v, err = ParseUtil.evaluate(v_str, variables) if err: return f"Invalid value '{v_str}' for '{ee}' in parameter '{NAME}'." if ee == kw.DEFAULT: if self.val[NAME][kw.DEFAULT] is not None: return f"Default value for '{NAME}' can only be stated once." self.val[NAME][kw.DEFAULT] = v elif ee.count('->') == 1: e1, e2 = ee.split('->') if e1 not in self.val[kw.STIMULUS_ELEMENTS]: return f"Error in parameter '{NAME}': '{e1}' is an invalid stimulus element." if e2 not in self.val[kw.STIMULUS_ELEMENTS]: return f"Error in parameter '{NAME}': '{e2}' is an invalid stimulus element." if self.val[NAME][(e1, e2)] is not None: return f"Duplicate of {e1}->{e2} in '{NAME}'." self.val[NAME][(e1, e2)] = v else: return f"Invalid string '{ee}' in parameter '{NAME}'." if not to_be_continued: # Set the default value for non-set stimulus-stimulus pairs err = self._set_default_values(NAME) if err: return err return None # No error
def _parse_stimulus_response_values(self, NAME, sr_str, variables, to_be_continued, is_appending): """ Parse the string sr_str with a value for stimulus-response pairs. Example: "S1->R1: 1.23, S2->R1:3.45, default:1" sets the parameter to {('S1','R1'):1.23, ('S1','R2'):1, ('S2','R1'):3.45, ('S2','R2'):1} under the assumption that behaviors = {'R1', 'R2'} and stimulus_elements = {'S1', 'S2'} """ if not self.val[kw.STIMULUS_ELEMENTS]: return f"The parameter 'stimulus_elements' must be assigned before the parameter '{NAME}'." if not self.val[kw.BEHAVIORS]: return f"The parameter 'behaviors' must be assigned before the parameter '{NAME}'." # Create and populate the struct with None values if not is_appending: self.val[NAME] = dict() for e in self.val[kw.STIMULUS_ELEMENTS]: for b in self.val[kw.BEHAVIORS]: self.val[NAME][(e, b)] = None self.val[NAME][kw.DEFAULT] = None single_v, _ = ParseUtil.evaluate(sr_str, variables) if single_v is not None: if is_appending: return f"A single value for '{NAME}' cannot follow other values." elif to_be_continued: return f"A single value for '{NAME}' cannot be followed by other values." else: for key in self.val[NAME]: self.val[NAME][key] = single_v self.val[NAME].pop(kw.DEFAULT) else: vs = ParseUtil.comma_split(sr_str) vs = [x.strip() for x in vs] for eb_v_str in vs: # eb_v_str is 'e->b:value' or 'default:value' if eb_v_str.count(':') != 1: return f"Expected 'x->y:value' or 'default:value' in '{NAME}', got '{eb_v_str}'." eb, v_str = eb_v_str.split(':') eb = eb.strip() v_str = v_str.strip() v, err = ParseUtil.evaluate(v_str, variables) if err: return f"Invalid value '{v_str}' for '{eb}' in parameter '{NAME}'." if eb == kw.DEFAULT: if self.val[NAME][kw.DEFAULT] is not None: return f"Default value for '{NAME}' can only be stated once." self.val[NAME][kw.DEFAULT] = v elif eb.count('->') == 1: e, b = eb.split('->') if e not in self.val[kw.STIMULUS_ELEMENTS]: return f"Error in parameter '{NAME}': '{e}' is an invalid stimulus element." if b not in self.val[kw.BEHAVIORS]: return f"Error in parameter '{NAME}': '{b}' is an invalid behavior name." if self.val[NAME][(e, b)] is not None: return f"Duplicate of {e}->{b} in '{NAME}'." self.val[NAME][(e, b)] = v else: return f"Invalid string '{eb}' in parameter '{NAME}'." if not to_be_continued: # Set the default value for non-set stimulus-behavior pairs err = self._set_default_values(NAME) if err: return err return None # No error
def _is_valid_value(self, value_str): return ParseUtil.evaluate(value_str, self)
def next_stimulus(self, response, ignore_response_increment=False, preceeding_help_lines=None, omit_learn=None): # if not self.is_parsed: # raise Exception("Internal error: Cannot call Phase.next_stimulus" + # " before Phase.parse().") if preceeding_help_lines is None: preceeding_help_lines = list() if not ignore_response_increment: # if not self.is_first_line: if response is not None: self.event_counter.increment_count(response) self.event_counter.increment_count_line(response) self.event_counter.set_last_response(response) if self.first_stimulus_presented: variables_both = Variables.join(self.global_variables, self.local_variables) if self.stop_condition.is_met(variables_both, self.event_counter): return None, None, preceeding_help_lines, None if self.is_first_line: # assert(response is None) rowlbl = self.first_label self.is_first_line = False omit_learn = True # Since response is None, there is no learning (updating of v-values) to do else: rowlbl, omit_learn = self.curr_lineobj.next_line(response, self.global_variables, self.local_variables, self.event_counter) self.prev_linelabel = self.curr_lineobj.label self._make_current_line(rowlbl) stimulus = self.phase_lines[rowlbl].stimulus if stimulus is not None: for element, intensity in stimulus.items(): if type(intensity) is str: # element[var] where var is a (local) variable variables_both = Variables.join(self.global_variables, self.local_variables) stimulus[element], err = ParseUtil.evaluate(intensity, variables=variables_both) if err: raise ParseException(self.phase_lines[rowlbl].lineno, err) if rowlbl != self.prev_linelabel: self.event_counter.reset_count_line() self.event_counter.line_label = rowlbl self.event_counter.increment_count(rowlbl) self.event_counter.increment_count_line(rowlbl) if stimulus is None: # Help line action = self.phase_lines[rowlbl].action lineno = self.phase_lines[rowlbl].lineno self._perform_action(lineno, action) preceeding_help_lines.append(rowlbl) next_stimulus_out = self.next_stimulus(response, ignore_response_increment=True, preceeding_help_lines=preceeding_help_lines, omit_learn=omit_learn) stimulus, rowlbl, preceeding_help_lines, omit_learn_help = next_stimulus_out omit_learn = (omit_learn or omit_learn_help) else: for stimulus_element in stimulus: self.event_counter.increment_count(stimulus_element) self.event_counter.increment_count_line(stimulus_element) self.first_stimulus_presented = True return stimulus, rowlbl, preceeding_help_lines, omit_learn