def test_unit_conversion(self): self.assertEqual(unit_conversion('km', 'm'), (1000., 0.)) try: unit_conversion('km', 1.0) except RuntimeError as err: self.assertEqual(str(err), "Cannot convert to new units: '1.0'.") else: self.fail("Expecting RuntimeError")
def test_unit_conversion(self): self.assertEqual(unit_conversion('km', 'm'), (1000., 0.)) try: unit_conversion('km', 1.0) except ValueError as err: self.assertEqual(str(err), "The units '1.0' are invalid.") else: self.fail("Expecting RuntimeError")
def test_atto_seconds(self): # The unit 'as' was bugged because it is a python keyword. fact = unit_conversion('s', 'as') assert_near_equal(fact[0], 1e18) # Make sure regex for 'as' doesn't pick up partial words. fact = unit_conversion('aslug*as*as', 'aslug*zs*zs') assert_near_equal(fact[0], 1e6) # Make sure simplification works. simple = simplify_unit('m*as/as') self.assertEqual(simple, 'm') simple = simplify_unit('as**6/as**4') self.assertEqual(simple, 'as**2')
def get_val(self, name, units=None, indices=None): """ Get an output/input variable. Function is used if you want to specify display units. Parameters ---------- name : str Promoted or relative variable name in the root system's namespace. units : str, optional Units to convert to before upon return. indices : int or list of ints or tuple of ints or int ndarray or Iterable or None, optional Indices or slice to return. Returns ------- float or ndarray The requested output/input variable. """ val = self[name] if indices is not None: val = val[indices] if units is not None: base_units = self._get_units(name) simp_units = simplify_unit(units) if base_units is None: msg = "Can't express variable '{}' with units of 'None' in units of '{}'." raise TypeError(msg.format(name, simp_units)) try: scale, offset = unit_conversion(base_units, simp_units) except TypeError: msg = "Can't express variable '{}' with units of '{}' in units of '{}'." raise TypeError(msg.format(name, base_units, simp_units)) val = (val + offset) * scale return val
def _initialize(self, system): """ Allocate the global matrices. Parameters ---------- system : System Parent system to this jacobian. """ # var_indices are the *global* indices for variables on this proc is_top = system.pathname == '' abs2meta_in = system._var_abs2meta['input'] all_meta = system._var_allprocs_abs2meta self._int_mtx = int_mtx = self._matrix_class(system.comm, True) ext_mtx = self._matrix_class(system.comm, False) iproc = system.comm.rank out_ranges = self._out_ranges in_ranges = self._in_ranges abs2prom_out = system._var_abs2prom['output'] conns = {} if isinstance(system, Component) else system._conn_global_abs_in2out abs_key2shape = self._abs_key2shape # create the matrix subjacs for abs_key, info in self._subjacs_info.items(): res_abs_name, wrt_abs_name = abs_key # because self._subjacs_info is shared among all 'related' assembled jacs, # we use out_ranges (and later in_ranges) to weed out keys outside of this jac if res_abs_name not in out_ranges: continue res_offset, res_end = out_ranges[res_abs_name] res_size = res_end - res_offset if wrt_abs_name in abs2prom_out: out_offset, out_end = out_ranges[wrt_abs_name] out_size = out_end - out_offset shape = (res_size, out_size) int_mtx._add_submat(abs_key, info, res_offset, out_offset, None, shape) elif wrt_abs_name in in_ranges: if wrt_abs_name in conns: # connected input out_abs_name = conns[wrt_abs_name] if out_abs_name not in out_ranges: continue meta_in = abs2meta_in[wrt_abs_name] all_out_meta = all_meta['output'][out_abs_name] # calculate unit conversion in_units = meta_in['units'] out_units = all_out_meta['units'] if in_units and out_units and in_units != out_units: factor, _ = unit_conversion(out_units, in_units) if factor == 1.0: factor = None else: factor = None out_offset, out_end = out_ranges[out_abs_name] out_size = out_end - out_offset shape = (res_size, out_size) src_indices = abs2meta_in[wrt_abs_name]['src_indices'] if src_indices is not None: # need to add an entry for d(output)/d(source) # instead of d(output)/d(input). int_mtx is a square matrix whose # rows and columns map to output/resid vars only. abs_key2 = (res_abs_name, out_abs_name) shape = abs_key2shape(abs_key2) int_mtx._add_submat(abs_key, info, res_offset, out_offset, src_indices, shape, factor) elif not is_top: # input is connected to something outside current system in_offset, in_end = in_ranges[wrt_abs_name] # don't use global offsets for ext_mtx res_offset, res_end = out_ranges[res_abs_name] res_size = res_end - res_offset shape = (res_size, in_end - in_offset) ext_mtx._add_submat(abs_key, info, res_offset, in_offset, None, shape) out_size = len(system._outputs) int_mtx._build(out_size, out_size, system) if ext_mtx._submats: ext_mtx._build(out_size, len(system._vectors['input']['linear'])) else: ext_mtx = None self._ext_mtx[system.pathname] = ext_mtx
def _add_output_configure(self, name, units, shape, desc='', src=None): """ Add a single timeseries output. Can be called by parent groups in configure. Parameters ---------- name : str name of the variable in this component's namespace. shape : int or tuple or list or None Shape of this variable, only required if val is not an array. Default is None. units : str or None Units in which the output variables will be provided to the component during execution. Default is None, which means it has no units. desc : str description of the timeseries output variable. src : str The src path of the variables input, used to prevent redundant inputs. Returns ------- bool True if a new input was added for the output, or False if it reuses an existing input. """ input_num_nodes = self.input_num_nodes output_num_nodes = self.output_num_nodes added_source = False if name in self._vars: return False if src in self._sources: # If we're already pulling the source into this timeseries, use that as the # input for this output. input_name = self._sources[src] input_units = self._units[input_name] else: input_name = f'input_values:{name}' self.add_input(input_name, shape=(input_num_nodes, ) + shape, units=units, desc=desc) self._sources[src] = input_name input_units = self._units[input_name] = units added_source = True output_name = name self.add_output(output_name, shape=(output_num_nodes, ) + shape, units=units, desc=desc) self._vars[name] = (input_name, output_name, shape) size = np.prod(shape) rs = cs = np.arange(output_num_nodes * size, dtype=int) # There's a chance that the input for this output was pulled from another variable with # different units, so account for that with a conversion. if None in {input_units, units}: scale = 1.0 offset = 0 else: scale, offset = unit_conversion(input_units, units) self._conversion_factors[output_name] = scale, offset self.declare_partials(of=output_name, wrt=input_name, rows=rs, cols=cs, val=scale) return added_source
def add_var(self, name, shape, units, disc_src, col_src): """ Add a variable to be interleaved. In general these need to be variables whose values are stored separately for state discretization or collocation nodes (such as states or ODE outputs). Parameters ---------- name : str The name of variable as it should appear in the outputs of the component ('interleave_comp.all_values:{name}'). shape : tuple The shape of the variable at each instance in time. units : str The units of the variable. disc_src : str The source path of the variable's inputs at the discretization nodes. col_src : str The source path of the variable's inputs at the collocation nodes. Returns ------- bool True if the variable was added to the interleave comp, False if not due to it already being there. """ if name in self._varnames: return False num_disc_nodes = self.options['grid_data'].subset_num_nodes['state_disc'] num_col_nodes = self.options['grid_data'].subset_num_nodes['col'] num_nodes = self.options['grid_data'].subset_num_nodes['all'] added_source = False size = np.prod(shape) self._varnames[name] = {} self._varnames[name]['state_disc'] = f'disc_values:{name}' self._varnames[name]['col'] = f'col_values:{name}' self._varnames[name]['all'] = f'all_values:{name}' # Check to see if the given disc source has already been used # We'll assume that the col source will be the same as well, no need to check both. if disc_src in self._sources['state_disc']: self._varnames[name]['state_disc'] = self._sources['state_disc'][disc_src] self._varnames[name]['col'] = self._sources['col'][col_src] input_units = self._units[self._varnames[name]['state_disc']] else: self.add_input( name=self._varnames[name]['state_disc'], shape=(num_disc_nodes,) + shape, desc=f'Values of {name} at discretization nodes', units=units) self.add_input( name=self._varnames[name]['col'], shape=(num_col_nodes,) + shape, desc=f'Values of {name} at collocation nodes', units=units) self._sources['state_disc'][disc_src] = self._varnames[name]['state_disc'] self._sources['col'][col_src] = self._varnames[name]['col'] input_units = self._units[self._varnames[name]['state_disc']] = units added_source = True self.add_output( name=self._varnames[name]['all'], shape=(num_nodes,) + shape, desc=f'Values of {name} at all nodes', units=units) start_rows = self.options['grid_data'].subset_node_indices['state_disc'] * size r = (start_rows[:, np.newaxis] + np.arange(size, dtype=int)).ravel() c = np.arange(size * num_disc_nodes, dtype=int) # There's a chance that the input for this output was pulled from another variable with # different units, so account for that with a conversion. if None in {input_units, units}: scale = 1.0 offset = 0 else: scale, offset = unit_conversion(input_units, units) self._conversion_factors[self._varnames[name]['all']] = scale, offset self.declare_partials(of=self._varnames[name]['all'], wrt=self._varnames[name]['state_disc'], rows=r, cols=c, val=scale) start_rows = self.options['grid_data'].subset_node_indices['col'] * size r = (start_rows[:, np.newaxis] + np.arange(size, dtype=int)).ravel() c = np.arange(size * num_col_nodes, dtype=int) self.declare_partials(of=self._varnames[name]['all'], wrt=self._varnames[name]['col'], rows=r, cols=c, val=scale) return added_source