def dimensions_and_type_from_string(unit_string): ''' Returns the physical dimensions that results from evaluating a string like "siemens / metre ** 2", allowing for the special string "1" to signify dimensionless units, the string "boolean" for a boolean and "integer" for an integer variable. Parameters ---------- unit_string : str The string that should evaluate to a unit Returns ------- d, type : (`Dimension`, {FLOAT, INTEGER or BOOL}) The resulting physical dimensions and the type of the variable. Raises ------ ValueError If the string cannot be evaluated to a unit. ''' # Lazy import to avoid circular dependency from brian2.core.namespace import DEFAULT_UNITS global _base_units # we only want to do this once global _single_base_units if _base_units is None: base_units_for_dims = {} _base_units = collections.OrderedDict() for unit_name, unit in DEFAULT_UNITS.iteritems(): if float(unit) == 1.0: _base_units[unit_name] = unit # Go through it a second time -- we only want to display one unit per # dimensionality to the user and don't bother displaying powered units # (meter2, meter3, ...) for unit in _base_units.itervalues(): if (unit.dim not in base_units_for_dims and repr(unit)[-1] not in ['2', '3']): base_units_for_dims[unit.dim] = unit _single_base_units = sorted( [repr(unit) for unit in base_units_for_dims.itervalues()]) unit_string = unit_string.strip() # Special case: dimensionless unit if unit_string == '1': return DIMENSIONLESS, FLOAT # Another special case: boolean variable if unit_string == 'boolean': return DIMENSIONLESS, BOOLEAN if unit_string == 'bool': raise TypeError("Use 'boolean' not 'bool' as the unit for a boolean " "variable.") # Yet another special case: integer variable if unit_string == 'integer': return DIMENSIONLESS, INTEGER # Check first whether the expression only refers to base units identifiers = get_identifiers(unit_string) for identifier in identifiers: if identifier not in _base_units: if identifier in DEFAULT_UNITS: # A known unit, but not a base unit base_unit = get_unit(DEFAULT_UNITS[identifier].dim) if not repr(base_unit) in _base_units: # Make sure that we don't suggest a unit that is not allowed # (should not happen, normally) base_unit = Unit(1, dim=base_unit.dim) raise ValueError( ('Unit specification refers to ' '"{identifier}", but this is not a base ' 'unit. Use "{base_unit}" ' 'instead.').format(identifier=identifier, base_unit=repr(base_unit))) else: # Not a known unit allowed = ', '.join(_single_base_units) raise ValueError(('Unit specification refers to ' '"{identifier}", but this is not a base ' 'unit. The following base units are ' 'allowed: {allowed_units} (plus some ' 'variants of these, e.g. "Hz" instead of ' '"hertz", or "meter" instead of ' '"metre").').format(identifier=identifier, allowed_units=allowed)) try: evaluated_unit = eval(unit_string, _base_units) except Exception as ex: raise ValueError(('Could not interpret "%s" as a unit specification: ' '%s') % (unit_string, ex)) # Check whether the result is a unit if not isinstance(evaluated_unit, Unit): if isinstance(evaluated_unit, Quantity): raise ValueError( ('"%s" does not evaluate to a unit but to a ' 'quantity -- make sure to only use units, e.g. ' '"siemens/metre**2" and not "1 * siemens/metre**2"') % unit_string) else: raise ValueError( ('"%s" does not evaluate to a unit, the result ' 'has type %s instead.' % (unit_string, type(evaluated_unit)))) # No error has been raised, all good return evaluated_unit.dim, FLOAT
def dimensions_and_type_from_string(unit_string): ''' Returns the physical dimensions that results from evaluating a string like "siemens / metre ** 2", allowing for the special string "1" to signify dimensionless units, the string "boolean" for a boolean and "integer" for an integer variable. Parameters ---------- unit_string : str The string that should evaluate to a unit Returns ------- d, type : (`Dimension`, {FLOAT, INTEGER or BOOL}) The resulting physical dimensions and the type of the variable. Raises ------ ValueError If the string cannot be evaluated to a unit. ''' # Lazy import to avoid circular dependency from brian2.core.namespace import DEFAULT_UNITS global _base_units_with_alternatives global _base_units if _base_units_with_alternatives is None: base_units_for_dims = {} for unit_name, unit in reversed(DEFAULT_UNITS.items()): if float(unit) == 1.0 and repr(unit)[-1] not in ['2', '3']: if unit.dim in base_units_for_dims: if unit_name not in base_units_for_dims[unit.dim]: base_units_for_dims[unit.dim].append(unit_name) else: base_units_for_dims[unit.dim] = [repr(unit)] if unit_name != repr(unit): base_units_for_dims[unit.dim].append(unit_name) alternatives = sorted( [tuple(values) for values in base_units_for_dims.itervalues()]) _base_units = dict([(v, DEFAULT_UNITS[v]) for values in alternatives for v in values]) # Create a string that lists all allowed base units alternative_strings = [] for units in alternatives: string = units[0] if len(units) > 1: string += ' ({other_units})'.format( other_units=', '.join(units[1:])) alternative_strings.append(string) _base_units_with_alternatives = ', '.join(alternative_strings) unit_string = unit_string.strip() # Special case: dimensionless unit if unit_string == '1': return DIMENSIONLESS, FLOAT # Another special case: boolean variable if unit_string == 'boolean': return DIMENSIONLESS, BOOLEAN if unit_string == 'bool': raise TypeError("Use 'boolean' not 'bool' as the unit for a boolean " "variable.") # Yet another special case: integer variable if unit_string == 'integer': return DIMENSIONLESS, INTEGER # Check first whether the expression only refers to base units identifiers = get_identifiers(unit_string) for identifier in identifiers: if identifier not in _base_units: if identifier in DEFAULT_UNITS: # A known unit, but not a base unit base_unit = get_unit(DEFAULT_UNITS[identifier].dim) if not repr(base_unit) in _base_units: # Make sure that we don't suggest a unit that is not allowed # (should not happen, normally) base_unit = Unit(1, dim=base_unit.dim) raise ValueError( ('Unit specification refers to ' '"{identifier}", but this is not a base ' 'unit. Use "{base_unit}" ' 'instead.').format(identifier=identifier, base_unit=repr(base_unit))) else: # Not a known unit raise ValueError( ('Unit specification refers to ' '"{identifier}", but this is not a base ' 'unit. The following base units are ' 'allowed: ' '{allowed_units}.').format( identifier=identifier, allowed_units=_base_units_with_alternatives)) try: evaluated_unit = eval(unit_string, _base_units) except Exception as ex: raise ValueError(('Could not interpret "%s" as a unit specification: ' '%s') % (unit_string, ex)) # Check whether the result is a unit if not isinstance(evaluated_unit, Unit): if isinstance(evaluated_unit, Quantity): raise ValueError( ('"%s" does not evaluate to a unit but to a ' 'quantity -- make sure to only use units, e.g. ' '"siemens/metre**2" and not "1 * siemens/metre**2"') % unit_string) else: raise ValueError( ('"%s" does not evaluate to a unit, the result ' 'has type %s instead.' % (unit_string, type(evaluated_unit)))) # No error has been raised, all good return evaluated_unit.dim, FLOAT
def dimensions_and_type_from_string(unit_string): ''' Returns the physical dimensions that results from evaluating a string like "siemens / metre ** 2", allowing for the special string "1" to signify dimensionless units, the string "boolean" for a boolean and "integer" for an integer variable. Parameters ---------- unit_string : str The string that should evaluate to a unit Returns ------- d, type : (`Dimension`, {FLOAT, INTEGER or BOOL}) The resulting physical dimensions and the type of the variable. Raises ------ ValueError If the string cannot be evaluated to a unit. ''' # Lazy import to avoid circular dependency from brian2.core.namespace import DEFAULT_UNITS global _base_units_with_alternatives global _base_units if _base_units_with_alternatives is None: base_units_for_dims = {} for unit_name, unit in reversed(DEFAULT_UNITS.items()): if float(unit) == 1.0 and repr(unit)[-1] not in ['2', '3']: if unit.dim in base_units_for_dims: if unit_name not in base_units_for_dims[unit.dim]: base_units_for_dims[unit.dim].append(unit_name) else: base_units_for_dims[unit.dim] = [repr(unit)] if unit_name != repr(unit): base_units_for_dims[unit.dim].append(unit_name) alternatives = sorted([tuple(values) for values in base_units_for_dims.itervalues()]) _base_units = dict([(v, DEFAULT_UNITS[v]) for values in alternatives for v in values]) # Create a string that lists all allowed base units alternative_strings = [] for units in alternatives: string = units[0] if len(units) > 1: string += ' ({other_units})'.format(other_units=', '.join(units[1:])) alternative_strings.append(string) _base_units_with_alternatives = ', '.join(alternative_strings) unit_string = unit_string.strip() # Special case: dimensionless unit if unit_string == '1': return DIMENSIONLESS, FLOAT # Another special case: boolean variable if unit_string == 'boolean': return DIMENSIONLESS, BOOLEAN if unit_string == 'bool': raise TypeError("Use 'boolean' not 'bool' as the unit for a boolean " "variable.") # Yet another special case: integer variable if unit_string == 'integer': return DIMENSIONLESS, INTEGER # Check first whether the expression only refers to base units identifiers = get_identifiers(unit_string) for identifier in identifiers: if identifier not in _base_units: if identifier in DEFAULT_UNITS: # A known unit, but not a base unit base_unit = get_unit(DEFAULT_UNITS[identifier].dim) if not repr(base_unit) in _base_units: # Make sure that we don't suggest a unit that is not allowed # (should not happen, normally) base_unit = Unit(1, dim=base_unit.dim) raise ValueError(('Unit specification refers to ' '"{identifier}", but this is not a base ' 'unit. Use "{base_unit}" ' 'instead.').format(identifier=identifier, base_unit=repr(base_unit))) else: # Not a known unit raise ValueError(('Unit specification refers to ' '"{identifier}", but this is not a base ' 'unit. The following base units are ' 'allowed: ' '{allowed_units}.').format(identifier=identifier, allowed_units=_base_units_with_alternatives)) try: evaluated_unit = eval(unit_string, _base_units) except Exception as ex: raise ValueError(('Could not interpret "%s" as a unit specification: ' '%s') % (unit_string, ex)) # Check whether the result is a unit if not isinstance(evaluated_unit, Unit): if isinstance(evaluated_unit, Quantity): raise ValueError(('"%s" does not evaluate to a unit but to a ' 'quantity -- make sure to only use units, e.g. ' '"siemens/metre**2" and not "1 * siemens/metre**2"') % unit_string) else: raise ValueError(('"%s" does not evaluate to a unit, the result ' 'has type %s instead.' % (unit_string, type(evaluated_unit)))) # No error has been raised, all good return evaluated_unit.dim, FLOAT