001/* A TypedCompositeActor that creates multiple instances of itself 002 during the preinitialize phase of model execution. 003 004 Copyright (c) 2003-2014 The Regents of the University of California and 005 Research in Motion Limited. 006 All rights reserved. 007 Permission is hereby granted, without written agreement and without 008 license or royalty fees, to use, copy, modify, and distribute this 009 software and its documentation for any purpose, provided that the above 010 copyright notice and the following two paragraphs appear in all copies 011 of this software. 012 013 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA OR RESEARCH IN MOTION 014 LIMITED BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, 015 INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS 016 SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA 017 OR RESEARCH IN MOTION LIMITED HAVE BEEN ADVISED OF THE POSSIBILITY OF 018 SUCH DAMAGE. 019 020 THE UNIVERSITY OF CALIFORNIA AND RESEARCH IN MOTION LIMITED 021 SPECIFICALLY DISCLAIM ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 022 THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 023 PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" 024 BASIS, AND THE UNIVERSITY OF CALIFORNIA AND RESEARCH IN MOTION 025 LIMITED HAVE NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 026 ENHANCEMENTS, OR MODIFICATIONS. 027 028 PT_COPYRIGHT_VERSION_2 029 COPYRIGHTENDKEY 030 031 */ 032package ptolemy.actor.lib.hoc; 033 034import java.util.Iterator; 035import java.util.LinkedList; 036import java.util.List; 037 038import ptolemy.actor.TypedCompositeActor; 039import ptolemy.actor.TypedIOPort; 040import ptolemy.actor.TypedIORelation; 041import ptolemy.data.BooleanToken; 042import ptolemy.data.IntToken; 043import ptolemy.data.expr.Parameter; 044import ptolemy.data.type.BaseType; 045import ptolemy.kernel.CompositeEntity; 046import ptolemy.kernel.util.Attribute; 047import ptolemy.kernel.util.IllegalActionException; 048import ptolemy.kernel.util.InternalErrorException; 049import ptolemy.kernel.util.KernelException; 050import ptolemy.kernel.util.Location; 051import ptolemy.kernel.util.NameDuplicationException; 052import ptolemy.kernel.util.Workspace; 053 054// Note: the (at least) single-space is needed in the javadoc below to 055// protect emacs' comment text formatting from a "{@link..." appearing 056// at the start of a line and disabling paragraph reformatting and 057// line-wrap (zk) 058/////////////////////////////////////////////////////////////////// 059//// MultiInstanceComposite 060 061/** 062 A {@link ptolemy.actor.TypedCompositeActor} that creates multiple 063 instances of itself during the preinitialize phase of model execution.<p> 064 065 A MultiInstanceComposite actor may be used to instantiate {@link 066 #nInstances} identical processing blocks within a model. This actor 067 (the "master") creates {@link #nInstances} - 1 additional 068 instances (clones) of itself during the {@link #preinitialize()} phase 069 of model execution and destroys these additional instances during model 070 {@link #wrapup()}. MultiInstanceComposite <em>must be opaque</em> (have 071 a director), so that its Actor interface methods (preinitialize(), ..., 072 wrapup()) are invoked during model initialization. Each instance may 073 refer to its {@link #instance} [0..{@link #nInstances}-1] parameter 074 which is set automatically by the master if it needs to know its 075 instance number.<p> 076 077 MultiInstanceComposite <em>input</em> ports must not be multiports (for 078 now) and may be connected to multiports or regular ports. During 079 preinitialize(), the master MultiInstanceComposite determines how its 080 input ports are connected, and creates additional relations in its 081 container (the model it is embedded in) to connect the input ports of 082 its clones (instances) to the same output port if that port is a 083 multiport. If that output port is a regular port, the clone's input 084 port is linked to the already existing relation between that output 085 port and the master's input port. MultiInstanceComposite 086 <em>output</em> ports must not be multiports (for now) and must be 087 connected to input multiports. The master MultiInstanceComposite 088 creates additional relations to connect the output ports of its clones 089 to the input port. Finally, after all these connections are made, the 090 master's preinitialize() calls preinitialize() of the clones.<p> 091 092 From here on until wrapup(), nothing special happens. Type resolution 093 occurs on all instances in the modified model, so does initialize() and 094 the computation of schedules by directors of the master and clones.<p> 095 096 During model wrapup(), the master MultiContextComposite deletes any 097 relations created, unlinks any ports if needed, and deletes the clones 098 it created. To re-synchronize vergil's model graph, an empty 099 ChangeRequest is also queued with the Manager.<p> 100 101 Actor parameters inside MultiInstanceComposite may refer to parameters 102 of the container model. This presents a problem during cloning() and 103 wrapup() where the container model's parameters are not in scope during 104 the clone's validateSettables() (unless the MultiInstanceComposite is 105 built as a moml class having its own set of parameters). This problem 106 is for now solved by providing a temporary scope copy using a 107 ScopeExtendingAttribute for the cloning() and wrapup() phases of the 108 clones.<p> 109 110 @author Zoltan Kemenczy, Sean Simmons, Research In Motion Limited 111 @version $Id$ 112 @since Ptolemy II 4.0 113 @Pt.ProposedRating Red (zkemenczy) 114 @Pt.AcceptedRating Red (cxh) 115 */ 116public class MultiInstanceComposite extends TypedCompositeActor { 117 /** Construct a MultiInstanceComposite actor in the specified 118 * workspace with no container and an empty string as a name. 119 * @param workspace The workspace of this object. 120 */ 121 public MultiInstanceComposite(Workspace workspace) { 122 super(workspace); 123 _construct(); 124 } 125 126 /** Construct a MultiInstanceComposite actor with the given container 127 * and name. 128 * @param container The container. 129 * @param name The name of this actor. 130 * @exception IllegalActionException If the actor cannot be contained 131 * by the proposed container. 132 * @exception NameDuplicationException If the container already has an 133 * actor with this name. 134 */ 135 public MultiInstanceComposite(CompositeEntity container, String name) 136 throws IllegalActionException, NameDuplicationException { 137 super(container, name); 138 _construct(); 139 } 140 141 /////////////////////////////////////////////////////////////////// 142 //// ports and parameters //// 143 144 /** The total number of instances to instantiate including instance 145 * 0 (the master copy). 146 */ 147 public Parameter nInstances; 148 149 /** The index of this instance. */ 150 public Parameter instance; 151 152 /** If true, show the clones. */ 153 public Parameter showClones; 154 155 /** Clone a "master copy" of this actor into the specified workspace 156 * - note that this is not used for creating the additional 157 * instances. 158 * @param workspace The workspace for the new object. 159 * @return A new actor. 160 * @exception CloneNotSupportedException If a derived class contains 161 * an attribute that cannot be cloned. 162 */ 163 @Override 164 public Object clone(Workspace workspace) throws CloneNotSupportedException { 165 MultiInstanceComposite newObject = (MultiInstanceComposite) super.clone( 166 workspace); 167 newObject._isMasterCopy = _isMasterCopy; 168 return newObject; 169 } 170 171 /** Call the base class to perform standard preinitialize(), and, if 172 * this is the master copy, proceed to create {@link #nInstances}-1 173 * additional copies, and link them to the same input/output ports 174 * this master is connected to. 175 * 176 * @exception IllegalActionException If cloning the additional 177 * copies fails, or if any ports are not connected to multiports. 178 */ 179 @Override 180 public void preinitialize() throws IllegalActionException { 181 if (!_isMasterCopy) { 182 //All initialization happens in the master. 183 return; 184 } 185 super.preinitialize(); 186 187 // Master only from here on 188 if (getDirector() == null || getDirector().getContainer() != this) { 189 throw new IllegalActionException(this, 190 getFullName() + "No director."); 191 } 192 // Get write permission on the workspace. 193 try { 194 _workspace.getWriteAccess(); 195 196 int N = ((IntToken) nInstances.getToken()).intValue(); 197 198 // Make sure instance is correct (ignore any user errors :) 199 instance.setToken(new IntToken(0)); 200 201 TypedCompositeActor container = (TypedCompositeActor) getContainer(); 202 203 // We first remove the superfluous clones 204 while (_clones.size() > N - 1) { 205 MultiInstanceComposite clone = _clones.get(N - 1); 206 Iterator<?> ports = clone.portList().iterator(); 207 while (ports.hasNext()) { 208 TypedIOPort port = (TypedIOPort) ports.next(); 209 Iterator<?> relations = port.linkedRelationList() 210 .iterator(); 211 while (relations.hasNext()) { 212 TypedIORelation relation = (TypedIORelation) relations 213 .next(); 214 215 // Use a different criterion to delete relation 216 // since the old one wouldn't work any more. 217 // Added by Gang Zhou. 218 TypedIOPort mirrorPort = (TypedIOPort) getPort( 219 port.getName()); 220 221 if (!port.isDeeplyConnected(mirrorPort)) { 222 //if (relation.linkedPortList().size() <= 2) { 223 // Delete the relation that was created in 224 // preinitialize() 225 try { 226 if (_debugging) { 227 _debug("Deleting " 228 + relation.getFullName()); 229 } 230 relation.setContainer(null); 231 } catch (NameDuplicationException ex) { 232 throw new InternalErrorException(ex); 233 } 234 } else { 235 // Unlink the clone's port from the relation 236 if (_debugging) { 237 _debug("Unlinking " + port.getFullName() 238 + " from " + relation.getFullName()); 239 } 240 port.unlink(relation); 241 } 242 } 243 } 244 245 // Now delete the clone itself 246 try { 247 if (_debugging) { 248 _debug("Deleting " + clone.getFullName()); 249 } 250 clone.setContainer(null); 251 } catch (NameDuplicationException ex) { 252 throw new InternalErrorException(ex); 253 } 254 _clones.remove(N - 1); 255 } 256 257 // Initialize the clones 258 for (MultiInstanceComposite clone : _clones) { 259 clone._preinitClone(); 260 } 261 262 // Now instantiate the clones and connect them to the model 263 for (int i = _clones.size() + 1; i < N; i++) { 264 MultiInstanceComposite clone = null; 265 266 try { 267 clone = (MultiInstanceComposite) _cloneClone( 268 container.workspace()); 269 } catch (CloneNotSupportedException ex) { 270 throw new IllegalActionException(this, ex, "Clone failed."); 271 } 272 273 try { 274 // See if we should draw the clone. 275 if (((BooleanToken) showClones.getToken()).booleanValue()) { 276 // Draw the clone beneath the master's location. 277 Location location = (Location) clone 278 .getAttribute("_location"); 279 if (location != null) { 280 double coords[] = location.getLocation(); 281 coords[1] += 60 * i; 282 location.setLocation(coords); 283 } 284 } else { 285 // Hide the clone. 286 try { 287 new Attribute(clone, "_hide"); 288 } catch (KernelException e) { 289 // This should not occur. Ignore if it does 290 // since the only downside is that the actor is 291 // rendered. 292 } 293 } 294 295 clone.setName(getName() + "_" + i); 296 clone.setContainer(container); 297 clone.validateSettables(); 298 299 if (_debugging) { 300 _debug("Cloned: " + clone.getFullName()); 301 } 302 303 // Clone all attached relations and link to same 304 // ports as the originals 305 Iterator<?> ports = portList().iterator(); 306 307 while (ports.hasNext()) { 308 TypedIOPort port = (TypedIOPort) ports.next(); 309 TypedIOPort newPort = (TypedIOPort) clone 310 .getPort(port.getName()); 311 List<?> relations = port.linkedRelationList(); 312 if (relations == null || relations.size() < 1) { 313 continue; 314 } 315 if (relations.size() > 1) { 316 throw new IllegalActionException(port, 317 "Can be linked to one relation only"); 318 } 319 320 TypedIORelation relation = (TypedIORelation) relations 321 .get(0); 322 TypedIORelation oldRelation = relation; 323 324 // Iterate through other ports that are connected to this port. 325 // If a connected port is a multiport, then we create 326 // a new relation to connect the clone's newPort 327 // to that multiport. Otherwise, we use the 328 // relation above to link newPort. 329 Iterator<?> otherPorts = relation.linkedPortList(port) 330 .iterator(); 331 332 // Added by Gang Zhou. If a port is connected to 333 // multiple other ports (through a single relation), 334 // only one relation should be created. 335 boolean isRelationCreated = false; 336 boolean isPortLinked = false; 337 338 while (otherPorts.hasNext()) { 339 TypedIOPort otherPort = (TypedIOPort) otherPorts 340 .next(); 341 342 if (port.isOutput() && !otherPort.isMultiport()) { 343 throw new IllegalActionException(this, 344 getFullName() + ".preinitialize(): " 345 + "output port " 346 + port.getName() 347 + "must be connected to a multi-port"); 348 } 349 350 // Modified by Gang Zhou so that the port can 351 // be connected to the otherPort either from inside 352 // or from outside. 353 boolean isInsideLinked = otherPort 354 .isInsideGroupLinked(oldRelation); 355 356 if (port.isInput() 357 && (!isInsideLinked && otherPort.isOutput() 358 || isInsideLinked 359 && otherPort.isInput()) 360 || port.isOutput() && (!isInsideLinked 361 && otherPort.isInput() 362 || isInsideLinked 363 && otherPort.isOutput())) { 364 if (otherPort.isMultiport()) { 365 if (!isRelationCreated) { 366 relation = new TypedIORelation( 367 container, 368 "r_" + getName() + "_" + i + "_" 369 + port.getName()); 370 relation.setPersistent(false); 371 isRelationCreated = true; 372 373 if (_debugging) { 374 _debug(port.getFullName() 375 + ": created relation " 376 + relation.getFullName()); 377 } 378 } 379 380 otherPort.link(relation); 381 } 382 383 if (!isPortLinked) { 384 newPort.link(relation); 385 isPortLinked = true; 386 387 if (_debugging) { 388 _debug(newPort.getFullName() 389 + ": linked to " 390 + relation.getFullName()); 391 } 392 } 393 } 394 } 395 } 396 397 // Let the clone know which instance it is 398 clone.instance.setToken(new IntToken(i)); 399 } catch (NameDuplicationException ex) { 400 throw new IllegalActionException(this, ex, 401 "couldn't clone/create"); 402 } 403 404 // The clone is preinitialized only if it has just been 405 // created, otherwise the current director schedule will 406 // initialize it. 407 clone._preinitClone(); 408 _clones.add(clone); 409 } 410 } finally { 411 _workspace.doneWriting(); 412 } 413 } 414 415 /////////////////////////////////////////////////////////////////// 416 //// private methods //// 417 418 /** Clone to create a copy of the master copy. */ 419 private Object _cloneClone(Workspace workspace) 420 throws CloneNotSupportedException { 421 MultiInstanceComposite newObject = (MultiInstanceComposite) super.clone( 422 workspace); 423 newObject._isMasterCopy = false; 424 // The following is necessary in case an exception occurs 425 // during execution because then wrapup might not properly complete. 426 newObject.setPersistent(false); 427 428 return newObject; 429 } 430 431 private void _construct() { 432 // The base class identifies the class name as TypedCompositeActor 433 // irrespective of the actual class name. We override that here. 434 setClassName("ptolemy.actor.lib.hoc.MultiInstanceComposite"); 435 436 try { 437 nInstances = new Parameter(this, "nInstances", new IntToken(1)); 438 instance = new Parameter(this, "instance", new IntToken(0)); 439 showClones = new Parameter(this, "showClones", 440 new BooleanToken(false)); 441 showClones.setTypeEquals(BaseType.BOOLEAN); 442 } catch (Exception ex) { 443 throw new InternalErrorException(this, ex, 444 "Problem setting up instances or nInstances parameter"); 445 } 446 447 _isMasterCopy = true; 448 _attachText("_iconDescription", 449 "<svg>\n" + "<rect x=\"-20\" y=\"-10\" width=\"60\" " 450 + "height=\"40\" style=\"fill:red\"/>\n" 451 + "<rect x=\"-18\" y=\"-8\" width=\"56\" " 452 + "height=\"36\" style=\"fill:lightgrey\"/>\n" 453 + "<rect x=\"-25\" y=\"-15\" width=\"60\" " 454 + "height=\"40\" style=\"fill:red\"/>\n" 455 + "<rect x=\"-23\" y=\"-13\" width=\"56\" " 456 + "height=\"36\" style=\"fill:lightgrey\"/>\n" 457 + "<rect x=\"-30\" y=\"-20\" width=\"60\" " 458 + "height=\"40\" style=\"fill:red\"/>\n" 459 + "<rect x=\"-28\" y=\"-18\" width=\"56\" " 460 + "height=\"36\" style=\"fill:lightgrey\"/>\n" 461 + "<rect x=\"-15\" y=\"-10\" width=\"10\" height=\"8\" " 462 + "style=\"fill:white\"/>\n" 463 + "<rect x=\"-15\" y=\"2\" width=\"10\" height=\"8\" " 464 + "style=\"fill:white\"/>\n" 465 + "<rect x=\"5\" y=\"-4\" width=\"10\" height=\"8\" " 466 + "style=\"fill:white\"/>\n" 467 + "<line x1=\"-5\" y1=\"-6\" x2=\"0\" y2=\"-6\"/>" 468 + "<line x1=\"-5\" y1=\"6\" x2=\"0\" y2=\"6\"/>" 469 + "<line x1=\"0\" y1=\"-6\" x2=\"0\" y2=\"6\"/>" 470 + "<line x1=\"0\" y1=\"0\" x2=\"5\" y2=\"0\"/>" 471 + "</svg>\n"); 472 } 473 474 private void _preinitClone() throws IllegalActionException { 475 super.preinitialize(); 476 } 477 478 /////////////////////////////////////////////////////////////////// 479 //// private variables //// 480 private List<MultiInstanceComposite> _clones = new LinkedList<MultiInstanceComposite>(); 481 482 private boolean _isMasterCopy = false; 483 484 //private String _scopeExtendingAttributeName = "_micScopeExtender"; 485}