def get_values(self, var, units=None): if units is not None and not valid_units(units): raise ValueError('{0} is not a valid set of units.'.format(units)) var_in_phase = True if var == 'time': var_type = 'indep' elif var in self.outputs['states']: var_type = 'states' elif var in self.outputs['controls']: var_type = 'controls' elif var in self.outputs['design_parameters']: var_type = 'design_parameters' elif var in self.outputs['input_parameters']: var_type = 'input_parameters' elif var in self.outputs['control_rates']: var_type = 'control_rates' elif var in self.outputs['ode']: var_type = 'ode' else: var_in_phase = False if not var_in_phase: raise ValueError('Variable "{0}" not found in phase ' 'simulation results.'.format(var)) output = convert_units(self.outputs[var_type][var]['value'], self.outputs[var_type][var]['units'], units) return output
def _data_weight_decomposition(variables: VariableList, owe=None): """ Returns the two level weight decomposition of MTOW and optionally the decomposition of owe subcategories. :param variables: instance containing variables information :param owe: value of OWE, if provided names of owe subcategories will be provided :return: variable values, names and optionally owe subcategories names """ category_values = [] category_names = [] owe_subcategory_names = [] for variable in variables.names(): name_split = variable.split(":") if isinstance(name_split, list) and len(name_split) == 4: if name_split[0] + name_split[1] + name_split[ 3] == "dataweightmass" and not ("aircraft" in name_split[2]): category_values.append( convert_units(variables[variable].value[0], variables[variable].units, "kg")) category_names.append(name_split[2]) if owe: owe_subcategory_names.append( name_split[2] + "<br>" + str(int(variables[variable].value[0])) + " [kg] (" + str(round(variables[variable].value[0] / owe * 100, 1)) + "%)") if owe: result = category_values, category_names, owe_subcategory_names else: result = category_values, category_names, None return result
def setUp(self): self.nn = 2 ivc = om.IndepVarComp() ivc.add_output(name='A', shape=(self.nn, 5, 3), units='ft') ivc.add_output(name='x', shape=(self.nn, 3), units='lbf') ivc.add_output(name='B', shape=(self.nn, 5, 3), units='m') ivc.add_output(name='y', shape=(self.nn, 3), units='N') mvp = om.MatrixVectorProductComp(vec_size=self.nn, A_shape=(5, 3), A_units='m', x_units='N', b_units='N*m') mvp.add_product('c', A_name='B', x_name='y', A_shape=(5, 3), vec_size=self.nn, A_units='m', x_units='N', b_units='N*m') model = om.Group() model.add_subsystem(name='ivc', subsys=ivc, promotes_outputs=['*']) model.add_subsystem(name='mat_vec_product_comp', subsys=mvp, promotes=['*']) self.p = om.Problem(model) self.p.setup(force_alloc_complex=True) A = np.random.rand(self.nn, 5, 3) x = np.random.rand(self.nn, 3) self.p['A'] = A self.p['x'] = x self.p['B'] = convert_units(A, 'ft', 'm') self.p['y'] = convert_units(x, 'lbf', 'N') self.p.run_model()
def setUp(self): class MyModel(om.Group): def setup(self): ivc = om.IndepVarComp() ivc.add_output(name='A', shape=(2, 5, 3), units='ft') ivc.add_output(name='x', shape=(2, 3), units='lbf') mvp = om.MatrixVectorProductComp(vec_size=2, A_shape=(5, 3), A_units='m', x_units='N', b_units='N*m') self.add_subsystem('ivc', ivc, promotes_outputs=['*']) self.add_subsystem('mvp', mvp, promotes=['*']) def configure(self): self.ivc.add_output(name='B', shape=(2, 5, 3), units='m') self.ivc.add_output(name='y', shape=(2, 3), units='N') self.mvp.add_product('c', A_name='B', x_name='y', A_shape=(5, 3), vec_size=2, A_units='m', x_units='N', b_units='N*m') self.p = om.Problem(MyModel()) self.p.setup(force_alloc_complex=True) A = np.random.rand(2, 5, 3) x = np.random.rand(2, 3) self.p['A'] = A self.p['x'] = x self.p['B'] = convert_units(A, 'ft', 'm') self.p['y'] = convert_units(x, 'lbf', 'N') self.p.run_model()
def test_results(self): for i in range(self.nn): # b = Ax A_i = self.p['A'][i, :, :] x_i = self.p['x'][i, :] b_i = self.p.get_val('mat_vec_product_comp.b', units='ft*lbf')[i, :] expected_i = np.dot(A_i, x_i) np.testing.assert_almost_equal(b_i, expected_i) # c = Ay y_i = self.p['y'][i, :] c_i = self.p.get_val('mat_vec_product_comp.c', units='N*m')[i, :] expected_i = np.dot(convert_units(A_i, 'ft', 'm'), y_i) np.testing.assert_almost_equal(c_i, expected_i) # b & c should match after unit conversion np.testing.assert_almost_equal(convert_units(b_i, 'ft*lbf', 'N*m'), c_i)
def test_units(self): import openmdao.api as om from openmdao.utils.units import convert_units n = 10 model = om.Group() model.add_subsystem('indep', om.IndepVarComp('x', val=range(n), units='ft')) model.add_subsystem('ks', om.KSComp(width=n, units='m')) model.connect('indep.x', 'ks.g') prob = om.Problem(model=model) prob.setup() prob.run_model() # KS is expressed in meters, while the independent variable 'x' is in feet assert_near_equal(prob['ks.KS'][0], convert_units(max(prob['indep.x']), 'ft', 'm'), tolerance=1e-8) assert_near_equal(convert_units(prob['ks.KS'][0], 'm', 'ft'), max(prob['indep.x']), tolerance=1e-8)
def _get_variable_values_with_new_units( variables: VariableIO, var_names_and_new_units: Dict[str, str] ): """ Returns the value of the requested variable names with respect to their new units in the order in which their were given. This function works only for variable of value with shape=1 or float. :param variables: instance containing variables information :param var_names_and_new_units: dictionnary of the variable names as keys and units as value :return: values of the requested variables with respect to their new units """ new_values = [] for variable_name, unit in var_names_and_new_units.items(): new_values.append( convert_units(variables[variable_name].value[0], variables[variable_name].units, unit,) ) return new_values
def test_results(self): for i in range(self.nn): # b = Ax A_i = self.p['A'][i, :, :] x_i = self.p['x'][i, :] b_i = self.p.get_val('mat_vec_product_comp.b', units='ft*lbf')[i, :] expected_i = np.dot(A_i, x_i) np.testing.assert_almost_equal(b_i, expected_i) # c = Bx B_i = self.p['B'][i, :, :] c_i = self.p.get_val('mat_vec_product_comp.c', units='N*m')[i, :] expected_i = np.dot(B_i, convert_units(x_i, 'lbf', 'N')) np.testing.assert_almost_equal(c_i, expected_i)
def _has_val_mismatch(discretes, names, units, vals): """ Return True if any of the given values don't match, subject to unit conversion. Parameters ---------- discretes : set-like Set of discrete variable names. names : list List of variable names. units : list List of units corresponding to names. vals : list List of values corresponding to names. Returns ------- bool True if a mismatch was found, otherwise False. """ if len(names) < 2: return False uset = set(units) if '' in uset and len(uset) > 1: # at least one case has no units and at least one does, so there must be a mismatch return True u0 = v0 = _UNSET for n, u, v in zip(names, units, vals): if n in discretes: continue if u0 is _UNSET: u0 = u v0 = v else: if u != u0: # convert units v = convert_units(v, u, new_units=u0) if np.linalg.norm(v - v0) > 1e-10: return True return False
def test_units_compute_totals(self): p = om.Problem() p.model.add_subsystem('stuff', om.ExecComp(['y = x', 'cy = x'], x={'units': 'inch'}, y={'units': 'kg'}, cy={'units': 'kg'}), promotes=['*']) p.model.add_design_var('x', units='ft') p.model.add_objective('y', units='lbm') p.model.add_constraint('cy', units='lbm', lower=0) p.setup() p['x'] = 1.0 p.run_model() J_driver = p.driver._compute_totals() fact = convert_units(1.0, 'kg/inch', 'lbm/ft') assert_near_equal(J_driver['stuff.y', 'x'][0,0], fact, 1e-5) assert_near_equal(J_driver['stuff.cy', 'x'][0,0], fact, 1e-5)
def check_variables(cls, variables: VariableList) -> List[CheckRecord]: """ Check values of provided variables against registered limits. :param variables: :return: the list of checks """ records: List[CheckRecord] = [] for var in variables: for limit_definitions in cls._limit_definitions.values(): if var.name in limit_definitions: limit_def = limit_definitions[var.name] value = convert_units(var.value, var.units, limit_def.units) if value < limit_def.lower: status = ValidityStatus.TOO_LOW limit = limit_def.lower elif value > limit_def.upper: status = ValidityStatus.TOO_HIGH limit = limit_def.upper else: status = ValidityStatus.OK limit = None records.append( CheckRecord( var.name, status, limit, limit_def.units, var.value, var.units, limit_definitions.source_file, limit_definitions.logger_name, )) return records
def mass_breakdown_sun_plot(aircraft_file_path: str, file_formatter=None): """ Returns a figure sunburst plot of the mass breakdown. On the left a MTOW sunburst and on the right a OWE sunburst. Different designs can be superposed by providing an existing fig. Each design can be provided a name. :param aircraft_file_path: path of data file :param file_formatter: the formatter that defines the format of data file. If not provided, default format will be assumed. :return: sunburst plot figure """ variables = VariableIO(aircraft_file_path, file_formatter).read() var_names_and_new_units = { "data:weight:aircraft:MTOW": "kg", "data:weight:aircraft:OWE": "kg", "data:weight:aircraft:payload": "kg", "data:weight:aircraft:sizing_onboard_fuel_at_takeoff": "kg", } # pylint: disable=unbalanced-tuple-unpacking # It is balanced for the parameters provided mtow, owe, payload, onboard_fuel_at_takeoff = _get_variable_values_with_new_units( variables, var_names_and_new_units) # TODO: Deal with this in a more generic manner ? if round(mtow, 6) == round(owe + payload + onboard_fuel_at_takeoff, 6): mtow = owe + payload + onboard_fuel_at_takeoff fig = make_subplots( 1, 2, specs=[[{ "type": "domain" }, { "type": "domain" }]], ) fig.add_trace( go.Sunburst( labels=[ "MTOW" + "<br>" + str(int(mtow)) + " [kg]", "payload" + "<br>" + str(int(payload)) + " [kg] (" + str(round(payload / mtow * 100, 1)) + "%)", "onboard_fuel_at_takeoff" + "<br>" + str(int(onboard_fuel_at_takeoff)) + " [kg] (" + str(round(onboard_fuel_at_takeoff / mtow * 100, 1)) + "%)", "OWE" + "<br>" + str(int(owe)) + " [kg] (" + str(round(owe / mtow * 100, 1)) + "%)", ], parents=[ "", "MTOW" + "<br>" + str(int(mtow)) + " [kg]", "MTOW" + "<br>" + str(int(mtow)) + " [kg]", "MTOW" + "<br>" + str(int(mtow)) + " [kg]", ], values=[mtow, payload, onboard_fuel_at_takeoff, owe], branchvalues="total", ), 1, 1, ) # Get data:weight 2-levels decomposition categories_values, categories_names, categories_labels = _data_weight_decomposition( variables, owe=owe) sub_categories_values = [] sub_categories_names = [] sub_categories_parent = [] for variable in variables.names(): name_split = variable.split(":") if isinstance(name_split, list) and len(name_split) >= 5: parent_name = name_split[2] if parent_name in categories_names and name_split[-1] == "mass": variable_name = "_".join(name_split[3:-1]) sub_categories_values.append( convert_units(variables[variable].value[0], variables[variable].units, "kg")) sub_categories_parent.append( categories_labels[categories_names.index(parent_name)]) sub_categories_names.append(variable_name) # Define figure data figure_labels = ["OWE" + "<br>" + str(int(owe)) + " [kg]"] figure_labels.extend(categories_labels) figure_labels.extend(sub_categories_names) figure_parents = [""] for _ in categories_names: figure_parents.append("OWE" + "<br>" + str(int(owe)) + " [kg]") figure_parents.extend(sub_categories_parent) figure_values = [owe] figure_values.extend(categories_values) figure_values.extend(sub_categories_values) # Plot figure fig.add_trace( go.Sunburst( labels=figure_labels, parents=figure_parents, values=figure_values, branchvalues="total", ), 1, 2, ) fig.update_layout(title_text="Mass Breakdown", title_x=0.5) return fig
def get_values(self, var, nodes=None, units=None): """ Retrieve the values of the given variable at the given subset of nodes. Parameters ---------- var : str The variable whose values are to be returned. This may be the name 'time', the name of a state, control, or parameter, or the path to a variable in the ODEFunction of the phase. nodes : str The name of the node subset. units : str The units in which the values should be expressed. Must be compatible with the corresponding units inside the phase. Returns ------- ndarray An array of the values at the requested node subset. The node index is the first dimension of the ndarray. """ if nodes is None: nodes = 'all' gd = self.grid_data var_type = self._classify_var(var) op = dict(self.list_outputs(explicit=True, values=True, units=True, shape=True, out_stream=None)) if units is not None: if not valid_units(units): raise ValueError('Units {0} is not a valid units identifier'.format(units)) var_prefix = '{0}.'.format(self.pathname) if self.pathname else '' path_map = {'time': 'time.{0}', 'state': 'indep_states.states:{0}', 'indep_control': 'control_interp_comp.control_values:{0}', 'input_control': 'control_interp_comp.control_values:{0}', 'design_parameter': 'design_params.design_parameters:{0}', 'input_parameter': 'input_params.input_parameters:{0}_out', 'control_rate': 'control_interp_comp.control_rates:{0}', 'control_rate2': 'control_interp_comp.control_rates:{0}', 'ode': 'rhs_all.{0}'} if var_type == 'state': var_path = var_prefix + path_map[var_type].format(var) output_units = op[var_path]['units'] output_value = convert_units(op[var_path]['value'][gd.input_maps['state_input_to_disc'], ...], output_units, units) elif var_type in ('input_control', 'indep_control'): var_path = var_prefix + path_map[var_type].format(var) output_units = op[var_path]['units'] vals = op[var_path]['value'] output_value = convert_units(vals, output_units, units) elif var_type in ('design_parameter', 'input_parameter', 'traj_design_parameter', 'traj_input_parameter'): var_path = var_prefix + path_map[var_type].format(var) output_units = op[var_path]['units'] output_value = convert_units(op[var_path]['value'], output_units, units) output_value = np.repeat(output_value, gd.num_nodes, axis=0) elif var_type == 'ode': rhs_all_outputs = dict(self.rhs_all.list_outputs(out_stream=None, values=True, shape=True, units=True)) prom2abs_all = self.rhs_all._var_allprocs_prom2abs_list abs_path_all = prom2abs_all['output'][var][0] output_value = rhs_all_outputs[abs_path_all]['value'] output_units = rhs_all_outputs[abs_path_all]['units'] output_value = convert_units(output_value, output_units, units) else: var_path = var_prefix + path_map[var_type].format(var) output_units = op[var_path]['units'] output_value = convert_units(op[var_path]['value'], output_units, units) # Always return a column vector if len(output_value.shape) == 1: output_value = np.reshape(output_value, (gd.num_nodes, 1)) return output_value[gd.subset_node_indices[nodes], ...]
def view_connections(root, outfile='connections.html', show_browser=True, show_values=True, precision=6, title=None): """ Generate a self-contained html file containing a detailed connection viewer. Optionally pops up a web browser to view the file. Parameters ---------- root : system or Problem The root for the desired tree. outfile : str, optional The name of the output html file. Defaults to 'connections.html'. show_browser : bool, optional If True, pop up a browser to view the generated html file. Defaults to True. show_values : bool, optional If True, retrieve the values and display them. precision : int, optional Sets the precision for displaying array values. title : str, optional Sets the title of the web page. """ if MPI and MPI.COMM_WORLD.rank != 0: return # since people will be used to passing the Problem as the first arg to # the N2 diagram funct, allow them to pass a Problem here as well. if isinstance(root, Problem): system = root.model else: system = root input_srcs = system._problem_meta['connections'] connections = { tgt: src for tgt, src in input_srcs.items() if src is not None } src2tgts = defaultdict(list) units = {} for n, data in system._var_allprocs_abs2meta.items(): u = data.get('units', '') if u is None: u = '' units[n] = u vals = {} with printoptions(precision=precision, suppress=True, threshold=10000): for t in system._var_abs_names['input']: tmeta = system._var_abs2meta[t] idxs = tmeta['src_indices'] s = connections[t] if show_values: if s.startswith('_auto_ivc.'): val = system.get_val(t, indices=idxs, flat=True, get_remote=True, from_src=False) else: val = system.get_val(t, indices=idxs, flat=True, get_remote=True) # if there's a unit conversion, express the value in the # units of the target if units[t] and s in system._outputs: val = convert_units(val, units[s], units[t]) else: val = '' src2tgts[s].append(t) vals[t] = val NOCONN = '[NO CONNECTION]' vals[NOCONN] = '' src_systems = set() tgt_systems = set() for s in system._var_abs_names['output']: parts = s.split('.') for i in range(len(parts)): src_systems.add('.'.join(parts[:i])) for t in system._var_abs_names['input']: parts = t.split('.') for i in range(len(parts)): tgt_systems.add('.'.join(parts[:i])) src_systems = [{'name': n} for n in sorted(src_systems)] src_systems.insert(1, {'name': NOCONN}) tgt_systems = [{'name': n} for n in sorted(tgt_systems)] tgt_systems.insert(1, {'name': NOCONN}) tprom = system._var_allprocs_abs2prom['input'] sprom = system._var_allprocs_abs2prom['output'] table = [] idx = 1 # unique ID for use by Tabulator for tgt, src in connections.items(): usrc = units[src] utgt = units[tgt] if usrc != utgt: # prepend these with '!' so they'll be colored red if usrc: usrc = '!' + units[src] if utgt: utgt = '!' + units[tgt] row = { 'id': idx, 'src': src, 'sprom': sprom[src], 'sunits': usrc, 'val': _val2str(vals[tgt]), 'tunits': utgt, 'tprom': tprom[tgt], 'tgt': tgt } table.append(row) idx += 1 # add rows for unconnected sources for src in system._var_abs_names['output']: if src not in src2tgts: if show_values: v = _val2str(system._outputs[src]) else: v = '' row = { 'id': idx, 'src': src, 'sprom': sprom[src], 'sunits': units[src], 'val': v, 'tunits': '', 'tprom': NOCONN, 'tgt': NOCONN } table.append(row) idx += 1 if title is None: title = '' data = { 'title': title, 'table': table, 'show_values': show_values, } viewer = 'connect_table.html' code_dir = os.path.dirname(os.path.abspath(__file__)) libs_dir = os.path.join(code_dir, 'libs') style_dir = os.path.join(code_dir, 'style') with open(os.path.join(code_dir, viewer), "r") as f: template = f.read() with open(os.path.join(libs_dir, 'tabulator.min.js'), "r") as f: tabulator_src = f.read() with open(os.path.join(style_dir, 'tabulator.min.css'), "r") as f: tabulator_style = f.read() jsontxt = json.dumps(data) with open(outfile, 'w') as f: s = template.replace("<connection_data>", jsontxt) s = s.replace("<tabulator_src>", tabulator_src) s = s.replace("<tabulator_style>", tabulator_style) f.write(s) if show_browser: webview(outfile)
def get_values(self, var, phases=None, units=None, flat=False): """ Returns the values of the given variable from the given phases, if provided. If the variable is not present in one ore more phases, it will be returned as numpy.nan at each time step. Parameters ---------- var : str The variable whose values are to be returned. phases : Sequence, None The phases from which the values are desired. If None, included all Phases. units : str, None The units in which the values are desired. flat : bool If False return the values in a dictionary keyed by phase name. If True, return a single array incorporating values from all phases. Returns ------- dict or np.array If flat=False, a dictionary of the values of the variable in each phase will be returned, keyed by Phase name. If the values are not present in a subset of the phases, return numpy.nan at each time point in those phases. Raises ------ KeyError If the given variable is not found in any phase, a KeyError is raised. """ if units is not None and not valid_units(units): raise ValueError('{0} is not a valid set of units.'.format(units)) phases = self.get_phase_names() if phases is None else phases return_vals = dict([(phase_name, {}) for phase_name in phases]) var_in_traj = False times = {} time_units = None for phase_name in phases: var_in_phase = True # Gather times for the purposes of flattening the returned values # Note the adjustment to the last time, for the purposes of sorting only if time_units is None: time_units = self.outputs['phases'][phase_name]['indep'][ 'time']['units'] times[phase_name] = convert_units( self.outputs['phases'][phase_name]['indep']['time']['value'], self.outputs['phases'][phase_name]['indep']['time']['units'], time_units) times[phase_name][-1, ...] -= 1.0E-15 if var == 'time': var_type = 'indep' elif var in self.outputs['phases'][phase_name]['states']: var_type = 'states' elif var in self.outputs['phases'][phase_name]['controls']: var_type = 'controls' elif var in self.outputs['phases'][phase_name][ 'design_parameters']: var_type = 'design_parameters' elif var in self.outputs['phases'][phase_name]['input_parameters']: var_type = 'input_parameters' elif var.endswith('_rate') \ and var[:-5] in self.outputs['phases'][phase_name]['controls']: var_type = 'control_rates' elif var.endswith('_rate2') \ and var[:-6] in self.outputs['phases'][phase_name]['controls']: var_type = 'control_rates' elif var in self.outputs['phases'][phase_name]['ode']: var_type = 'ode' else: var_in_phase = False if var_in_phase: var_in_traj = True output = convert_units( self.outputs['phases'][phase_name][var_type][var]['value'], self.outputs['phases'][phase_name][var_type][var]['units'], units) else: indep_var = list( self.outputs['phases'][phase_name]['indep'].keys())[0] n = len(self.outputs['phases'][phase_name]['indep'][indep_var] ['value']) output = np.empty(n) output[:] = np.nan if not var_in_traj: raise KeyError('Variable "{0}" not found in trajectory ' 'simulation results.'.format(var)) return_vals[phase_name] = output if flat: time_array = np.concatenate([times[pname] for pname in phases]) sort_idxs = np.argsort(time_array, axis=0).ravel() return_vals = np.concatenate( [return_vals[pname] for pname in phases])[sort_idxs, ...] return return_vals
def view_connections(root, outfile='connections.html', show_browser=True, src_filter='', tgt_filter='', precision=6): """ Generate a self-contained html file containing a detailed connection viewer. Optionally pops up a web browser to view the file. Parameters ---------- root : system or Problem The root for the desired tree. outfile : str, optional The name of the output html file. Defaults to 'connections.html'. show_browser : bool, optional If True, pop up a browser to view the generated html file. Defaults to True. src_filter : str, optional If defined, use this as the initial value for the source system filter. tgt_filter : str, optional If defined, use this as the initial value for the target system filter. precision : int, optional Sets the precision for displaying array values. """ if MPI and MPI.COMM_WORLD.rank != 0: return # since people will be used to passing the Problem as the first arg to # the N2 diagram funct, allow them to pass a Problem here as well. if isinstance(root, Problem): system = root.model else: system = root input_srcs = system._conn_global_abs_in2out connections = { tgt: src for tgt, src in iteritems(input_srcs) if src is not None } src2tgts = defaultdict(list) units = { n: data.get('units', '') for n, data in iteritems(system._var_allprocs_abs2meta) } vals = {} with printoptions(precision=precision, suppress=True, threshold=10000): for t in system._var_abs_names['input']: tmeta = system._var_abs2meta[t] idxs = tmeta['src_indices'] if t in connections: s = connections[t] val = _get_output(system, s, idxs) # if there's a unit conversion, express the value in the # units of the target if units[t] and val != "<on remote_proc>": val = convert_units(val, units[s], units[t]) src2tgts[s].append(t) else: # unconnected param val = _get_input(system, t, None) if isinstance(val, np.ndarray): val = np.array2string(val) else: val = str(val) vals[t] = val noconn_srcs = sorted( (n for n in system._var_abs_names['output'] if n not in src2tgts), reverse=True) for s in noconn_srcs: vals[s] = str(system._outputs[s]) vals['NO CONNECTION'] = '' src_systems = set() tgt_systems = set() for s in system._var_abs_names['output']: parts = s.split('.') for i in range(len(parts)): src_systems.add('.'.join(parts[:i])) for t in system._var_abs_names['input']: parts = t.split('.') for i in range(len(parts)): tgt_systems.add('.'.join(parts[:i])) # reverse sort so that "NO CONNECTION" shows up at the bottom src2tgts['NO CONNECTION'] = sorted( [t for t in system._var_abs_names['input'] if t not in connections], reverse=True) src_systems = [{'name': n} for n in sorted(src_systems)] src_systems.insert(1, {'name': "NO CONNECTION"}) tgt_systems = [{'name': n} for n in sorted(tgt_systems)] tgt_systems.insert(1, {'name': "NO CONNECTION"}) data = { 'src2tgts': sorted(iteritems(src2tgts)), 'proms': None, 'units': units, 'vals': vals, 'src_systems': src_systems, 'tgt_systems': tgt_systems, 'noconn_srcs': noconn_srcs, 'src_filter': src_filter, 'tgt_filter': tgt_filter, } viewer = 'connect_table.html' code_dir = os.path.dirname(os.path.abspath(__file__)) with open(os.path.join(code_dir, viewer), "r") as f: template = f.read() graphjson = json.dumps(data) with open(outfile, 'w') as f: s = template.replace("<connection_data>", graphjson) f.write(s) if show_browser: webview(outfile)
def view_connections(root, outfile='connections.html', show_browser=True, src_filter='', tgt_filter='', precision=6): """ Generates a self-contained html file containing a detailed connection viewer. Optionally pops up a web browser to view the file. Parameters ---------- root : system or Problem The root for the desired tree. outfile : str, optional The name of the output html file. Defaults to 'connections.html'. show_browser : bool, optional If True, pop up a browser to view the generated html file. Defaults to True. src_filter : str, optional If defined, use this as the initial value for the source system filter. tgt_filter : str, optional If defined, use this as the initial value for the target system filter. precision : int, optional Sets the precision for displaying array values. """ # since people will be used to passing the Problem as the first arg to # the N2 diagram funct, allow them to pass a Problem here as well. if isinstance(root, Problem): system = root.model else: system = root input_srcs = system._conn_global_abs_in2out connections = { tgt: src for tgt, src in iteritems(input_srcs) if src is not None } src2tgts = {} units = {n: data.get('units','') for n, data in iteritems(system._var_allprocs_abs2meta)} vals = {} with printoptions(precision=precision, suppress=True, threshold=10000): for t in system._var_abs_names['input']: tmeta = system._var_abs2meta[t] idxs = tmeta['src_indices'] flat = tmeta['flat_src_indices'] if idxs is None: idxs = np.arange(tmeta['size'], dtype=int) if t in connections: s = connections[t] val = system._outputs[s] if isinstance(val, np.ndarray): val = system._outputs[s].flatten()[idxs] else: val = system._outputs[s] # if there's a unit conversion, express the value in the # units of the target if units[t]: val = convert_units(val, units[s], units[t]) if s not in src2tgts: src2tgts[s] = [t] else: src2tgts[s].append(t) else: # unconnected param val = system._inputs[t] if isinstance(val, np.ndarray): val = np.array2string(val) else: val = str(val) vals[t] = val noconn_srcs = sorted((n for n in system._var_abs_names['output'] if n not in src2tgts), reverse=True) for s in noconn_srcs: vals[s] = str(system._outputs[s]) vals['NO CONNECTION'] = '' src_systems = set() tgt_systems = set() for s in system._var_abs_names['output']: parts = s.split('.') for i in range(len(parts)): src_systems.add('.'.join(parts[:i])) for t in system._var_abs_names['input']: parts = t.split('.') for i in range(len(parts)): tgt_systems.add('.'.join(parts[:i])) # reverse sort so that "NO CONNECTION" shows up at the bottom src2tgts['NO CONNECTION'] = sorted([t for t in system._var_abs_names['input'] if t not in connections], reverse=True) src_systems = [{'name':n} for n in sorted(src_systems)] src_systems.insert(1, {'name': "NO CONNECTION"}) tgt_systems = [{'name':n} for n in sorted(tgt_systems)] tgt_systems.insert(1, {'name': "NO CONNECTION"}) data = { 'src2tgts': sorted(iteritems(src2tgts)), 'proms': None, 'units': units, 'vals': vals, 'src_systems': src_systems, 'tgt_systems': tgt_systems, 'noconn_srcs': noconn_srcs, 'src_filter': src_filter, 'tgt_filter': tgt_filter, } viewer = 'connect_table.html' code_dir = os.path.dirname(os.path.abspath(__file__)) with open(os.path.join(code_dir, viewer), "r") as f: template = f.read() graphjson = json.dumps(data) with open(outfile, 'w') as f: s = template.replace("<connection_data>", graphjson) f.write(s) if show_browser: webview(outfile)
def get_values(self, var, nodes=None, units=None): """ Retrieve the values of the given variable at the given subset of nodes. Parameters ---------- var : str The variable whose values are to be returned. This may be the name 'time', the name of a state, control, or parameter, or the path to a variable in the ODEFunction of the phase. nodes : str The name of the node subset or None (default). units : str The units in which the values should be expressed. Must be compatible with the corresponding units inside the phase. Returns ------- ndarray An array of the values at the requested node subset. The node index is the first dimension of the ndarray. """ if nodes is None: nodes = 'all' gd = self.grid_data disc_node_idxs = gd.subset_node_indices['state_disc'] col_node_idxs = gd.subset_node_indices['col'] var_type = self._classify_var(var) op = dict( self.list_outputs(explicit=True, values=True, units=True, shape=True, out_stream=None)) if units is not None: if not valid_units(units): raise ValueError( 'Units {0} is not a valid units identifier'.format(units)) var_prefix = '{0}.'.format(self.pathname) if self.pathname else '' path_map = { 'time': 'time.{0}', 'state': ('indep_states.states:{0}', 'state_interp.state_col:{0}'), 'indep_control': 'control_interp_comp.control_values:{0}', 'input_control': 'control_interp_comp.control_values:{0}', 'design_parameter': 'design_params.design_parameters:{0}', 'input_parameter': 'input_params.input_parameters:{0}_out', 'control_rate': 'control_interp_comp.control_rates:{0}', 'control_rate2': 'control_interp_comp.control_rates:{0}', 'ode': ('rhs_disc.{0}', 'rhs_col.{0}') } if var_type == 'state': # State and RHS values need to be interleaved since disc and col values are not # available from the same output disc_path_fmt, col_path_fmt = path_map[var_type] disc_path = var_prefix + disc_path_fmt.format(var) col_path = var_prefix + col_path_fmt.format(var) state_shape = op[disc_path]['shape'][1:] disc_units = op[disc_path]['units'] disc_vals = op[disc_path]['value'] col_units = op[col_path]['units'] col_vals = op[col_path]['value'] # If units is none, use the units from the IndepVarComp if units is None: units = disc_units output_value = np.zeros((gd.num_nodes, ) + state_shape) output_value[disc_node_idxs, ...] = \ convert_units(disc_vals[gd.input_maps['state_input_to_disc'], ...], disc_units, units) output_value[col_node_idxs, ...] = convert_units(col_vals, col_units, units) elif var_type in ('indep_control', 'input_control'): var_path = var_prefix + path_map[var_type].format(var) output_units = op[var_path]['units'] vals = op[var_path]['value'] output_value = convert_units(vals, output_units, units) elif var_type in ('design_parameter', 'input_parameter', 'traj_design_parameter', 'traj_input_parameter'): var_path = var_prefix + path_map[var_type].format(var) output_units = op[var_path]['units'] output_value = convert_units(op[var_path]['value'], output_units, units) output_value = np.repeat(output_value, gd.num_nodes, axis=0) elif var_type == 'ode': rhs_disc_outputs = dict( self.rhs_disc.list_outputs(out_stream=None, values=True, shape=True, units=True)) rhs_col_outputs = dict( self.rhs_col.list_outputs(out_stream=None, values=True, shape=True, units=True)) prom2abs_disc = self.rhs_disc._var_allprocs_prom2abs_list prom2abs_col = self.rhs_col._var_allprocs_prom2abs_list # Is var in prom2abs_disc['output']? abs_path_disc = prom2abs_disc['output'][var][0] abs_path_col = prom2abs_col['output'][var][0] shape = rhs_disc_outputs[abs_path_disc]['shape'][1:] disc_units = rhs_disc_outputs[abs_path_disc]['units'] col_units = rhs_col_outputs[abs_path_col]['units'] output_value = np.zeros((gd.num_nodes, ) + shape) disc_vals = rhs_disc_outputs[abs_path_disc]['value'] col_vals = rhs_col_outputs[abs_path_col]['value'] output_value[disc_node_idxs, ...] = convert_units(disc_vals, disc_units, units) output_value[col_node_idxs, ...] = convert_units(col_vals, col_units, units) else: var_path = var_prefix + path_map[var_type].format(var) output_units = op[var_path]['units'] output_value = convert_units(op[var_path]['value'], output_units, units) # Always return a column vector if len(output_value.shape) == 1: output_value = np.reshape(output_value, (gd.num_nodes, 1)) return output_value[gd.subset_node_indices[nodes], ...]